UBERF-8425: Global accounts (#7573)

This commit is contained in:
Alexey Zinoviev
2025-02-04 10:31:03 +07:00
committed by GitHub
parent 83e5b16392
commit 2c64ceadb8
765 changed files with 19134 additions and 17955 deletions
+375 -355
View File
@@ -1,9 +1,10 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Analytics } from '@hcengineering/analytics'
import chunter from '@hcengineering/chunter'
import { CollaboratorClient } from '@hcengineering/collaborator-client'
import contact, { AvatarType, Person, PersonAccount } from '@hcengineering/contact'
import contact, { AvatarType, Person } from '@hcengineering/contact'
import core, {
Account,
PersonId,
AccountRole,
AttachedDoc,
Branding,
@@ -28,7 +29,7 @@ import core, {
TxWorkspaceEvent,
WithLookup,
WorkspaceEvent,
WorkspaceIdWithUrl,
WorkspaceUuid,
concatLink,
generateId,
groupByArray,
@@ -36,7 +37,9 @@ import core, {
toIdMap,
type Blob,
type MigrationState,
type TimeRateLimiter
type TimeRateLimiter,
type WorkspaceIds,
type WorkspaceDataId
} from '@hcengineering/core'
import github, {
DocSyncInfo,
@@ -86,6 +89,10 @@ import {
githubSyncVersion
} from './types'
import { equalExceptKeys } from './utils'
// TODO: FIXME
type PersonAccount = any
/**
* @public
*/
@@ -97,7 +104,8 @@ export class GithubWorker implements IntegrationManager {
triggerRequests: number = 0
authRequestSend = new Set<Ref<Account>>()
// TODO: FIXME
authRequestSend = new Set<any>()
triggerSync: () => void = () => {
this.triggerRequests++
@@ -128,11 +136,11 @@ export class GithubWorker implements IntegrationManager {
clearInterval(this.periodicTimer)
this.closing = true
this.ctx.warn('Closing', { workspace: this.workspace.name })
this.ctx.warn('Closing', { workspace: this.workspace })
this.triggerSync()
await Promise.all([await this.syncPromise, new Promise<void>((resolve) => setTimeout(resolve, 5000))])
this.ctx.warn('Closing Done', { workspace: this.workspace.name })
this.ctx.warn('Closing Done', { workspace: this.workspace })
await this.client.close()
}
@@ -140,8 +148,12 @@ export class GithubWorker implements IntegrationManager {
await this.liveQuery.refreshConnect(clean)
}
getWorkspaceId (): WorkspaceIdWithUrl {
return this.workspace
getWorkspaceId (): WorkspaceUuid {
return this.workspace.uuid
}
getWorkspaceUrl (): string {
return this.workspace.url
}
getBranding (): Branding | null {
@@ -174,9 +186,9 @@ export class GithubWorker implements IntegrationManager {
return ''
}
const frontUrl = this.getBranding()?.front ?? config.FrontURL
const refUrl = concatLink(frontUrl, `/browse/?workspace=${this.workspace.name}`)
const refUrl = concatLink(frontUrl, `/browse/?workspace=${this.workspace.uuid}`)
// TODO storage URL
const imageUrl = concatLink(frontUrl ?? config.FrontURL, `/files?workspace=${this.workspace.name}&file=`)
const imageUrl = concatLink(frontUrl ?? config.FrontURL, `/files?workspace=${this.workspace.uuid}&file=`)
const guestUrl = getPublicLinkUrl(this.workspace, frontUrl)
const json = parseMessageMarkdown(text ?? '', refUrl, imageUrl, guestUrl)
await preprocessor?.(json)
@@ -189,9 +201,9 @@ export class GithubWorker implements IntegrationManager {
}
return await markupToMarkdown(
text ?? '',
concatLink(this.getBranding()?.front ?? config.FrontURL, `/browse/?workspace=${this.workspace.name}`),
concatLink(this.getBranding()?.front ?? config.FrontURL, `/browse/?workspace=${this.workspace.uuid}`),
// TODO storage URL
concatLink(this.getBranding()?.front ?? config.FrontURL, `/files/${this.workspace.name}/`),
concatLink(this.getBranding()?.front ?? config.FrontURL, `/files/${this.workspace.uuid}/`),
preprocessor
)
}
@@ -251,79 +263,81 @@ export class GithubWorker implements IntegrationManager {
}
async _getAccountRaw (userInfo?: UserInfo | null): Promise<PersonAccount | undefined> {
// We need to sync by userInfo id to prevent parallel requests.
if (userInfo === null) {
// Ghost author.
return await this.getAccount({
id: 'ghost',
login: 'ghost',
avatarUrl: 'https://avatars.githubusercontent.com/u/10137?v=4',
email: '<EMAIL>',
name: 'Ghost'
})
}
if (userInfo?.login == null) {
return
}
const userName = (userInfo.name ?? userInfo.login)
.split(' ')
.map((it) => it.trim())
.reverse()
.join(',') // TODO: Convert first, last name
// TODO: FIXME
throw new Error('Not implemented')
// // We need to sync by userInfo id to prevent parallel requests.
// if (userInfo === null) {
// // Ghost author.
// return await this.getAccount({
// id: 'ghost',
// login: 'ghost',
// avatarUrl: 'https://avatars.githubusercontent.com/u/10137?v=4',
// email: '<EMAIL>',
// name: 'Ghost'
// })
// }
// if (userInfo?.login == null) {
// return
// }
// const userName = (userInfo.name ?? userInfo.login)
// .split(' ')
// .map((it) => it.trim())
// .reverse()
// .join(',') // TODO: Convert first, last name
const infos = await this.liveQuery.findOne(github.class.GithubUserInfo, { login: userInfo.login })
if (infos === undefined) {
await this._client.createDoc(github.class.GithubUserInfo, contact.space.Contacts, {
...userInfo
})
}
// const infos = await this.liveQuery.findOne(github.class.GithubUserInfo, { login: userInfo.login })
// if (infos === undefined) {
// await this._client.createDoc(github.class.GithubUserInfo, contact.space.Contacts, {
// ...userInfo
// })
// }
const account = await this.client
.getModel()
.findOne(contact.class.PersonAccount, { email: `github:${userInfo.login}` })
if (account !== undefined) {
const person = await this.liveQuery.findOne(contact.class.Person, { _id: account.person })
// We need to be sure employee are exists.
if (person === undefined) {
const person: Ref<Person> = await this.findPerson(userInfo, userName)
if (account.person !== person) {
await this._client.update(account, { person })
}
}
return account
} else {
// Check authorized users
const accountRecord = await this.platform.getAccount(userInfo.login)
if (accountRecord !== undefined) {
const authorizedId = accountRecord.accounts[this.workspace.name]
if (authorizedId !== undefined) {
const emp = await this._client.findOne(contact.class.PersonAccount, {
_id: authorizedId as Ref<PersonAccount>
})
if (emp !== undefined) {
// We need to create github account
const gid = await this._client.createDoc(contact.class.PersonAccount, core.space.Model, {
email: `github:${userInfo.login}`,
person: emp.person,
role: AccountRole.User
})
const acc = await this._client.findOne(contact.class.PersonAccount, { _id: gid })
return acc
}
}
}
// const account = await this.client
// .getModel()
// .findOne(contact.class.PersonAccount, { email: `github:${userInfo.login}` })
// if (account !== undefined) {
// const person = await this.liveQuery.findOne(contact.class.Person, { _id: account.person })
// // We need to be sure employee are exists.
// if (person === undefined) {
// const person: Ref<Person> = await this.findPerson(userInfo, userName)
// if (account.person !== person) {
// await this._client.update(account, { person })
// }
// }
// return account
// } else {
// // Check authorized users
// const accountRecord = await this.platform.getAccount(userInfo.login)
// if (accountRecord !== undefined) {
// const authorizedId = accountRecord.accounts[this.workspace.name]
// if (authorizedId !== undefined) {
// const emp = await this._client.findOne(contact.class.PersonAccount, {
// _id: authorizedId as Ref<PersonAccount>
// })
// if (emp !== undefined) {
// // We need to create github account
// const gid = await this._client.createDoc(contact.class.PersonAccount, core.space.Model, {
// email: `github:${userInfo.login}`,
// person: emp.person,
// role: AccountRole.User
// })
// const acc = await this._client.findOne(contact.class.PersonAccount, { _id: gid })
// return acc
// }
// }
// }
const person: Ref<Person> | undefined = await this.findPerson(userInfo, userName)
// const person: Ref<Person> | undefined = await this.findPerson(userInfo, userName)
// We need to create email account
const id = await this._client.createDoc(contact.class.PersonAccount, core.space.Model, {
email: `github:${userInfo.login}`,
person,
role: AccountRole.User
})
const acc = await this.client.getModel().findOne(contact.class.PersonAccount, { _id: id })
return acc
}
// // We need to create email account
// const id = await this._client.createDoc(contact.class.PersonAccount, core.space.Model, {
// email: `github:${userInfo.login}`,
// person,
// role: AccountRole.User
// })
// const acc = await this.client.getModel().findOne(contact.class.PersonAccount, { _id: id })
// return acc
// }
}
private constructor (
@@ -334,7 +348,7 @@ export class GithubWorker implements IntegrationManager {
readonly client: Client,
readonly app: App,
readonly storageAdapter: StorageAdapter,
readonly workspace: WorkspaceIdWithUrl,
readonly workspace: WorkspaceIds,
readonly branding: Branding | null,
readonly periodicSyncInterval = 60 * 60 * 1000
) {
@@ -343,7 +357,7 @@ export class GithubWorker implements IntegrationManager {
this.repositoryManager = new RepositorySyncMapper(this.ctx.newChild('repository', {}), this._client, this.app)
this.collaborator = createCollaboratorClient(this.workspace)
this.collaborator = createCollaboratorClient(this.workspace.uuid)
this.personMapper = new UsersSyncManager(this.ctx.newChild('users', {}), this._client, this.liveQuery)
@@ -370,10 +384,11 @@ export class GithubWorker implements IntegrationManager {
_class: [chunter.class.ChatMessage],
mapper: new CommentSyncManager(this.ctx.newChild('comment', {}), this._client, this.liveQuery)
},
{
_class: [contact.class.PersonAccount],
mapper: this.personMapper
},
// TODO: FIXME
// {
// _class: [contact.class.PersonAccount],
// mapper: this.personMapper
// },
{
_class: [github.class.GithubReview],
mapper: new ReviewSyncManager(this.ctx.newChild('review', {}), this._client, this.liveQuery)
@@ -407,219 +422,228 @@ export class GithubWorker implements IntegrationManager {
this.periodicSyncPromise = undefined
}
private async findPerson (userInfo: UserInfo, userName: string): Promise<Ref<Person>> {
let person: Ref<Person> | undefined
// try to find by account.
if (userInfo.email != null && userInfo.email.trim().length > 0) {
const personAccount = await this.client.getModel().findOne(contact.class.PersonAccount, { email: userInfo.email })
person = personAccount?.person
}
// private async findPerson (userInfo: UserInfo, userName: string): Promise<Ref<Person>> {
// let person: Ref<Person> | undefined
// // try to find by account.
// if (userInfo.email != null && userInfo.email.trim().length > 0) {
// const personAccount = await this.client.getModel().findOne(contact.class.PersonAccount, { email: userInfo.email })
// person = personAccount?.person
// }
if (person === undefined) {
const channel = await this.liveQuery.findOne(contact.class.Channel, {
provider: contact.channelProvider.GitHub,
value: userInfo.login
})
person = channel?.attachedTo as Ref<Person>
}
// if (person === undefined) {
// const channel = await this.liveQuery.findOne(contact.class.Channel, {
// provider: contact.channelProvider.GitHub,
// value: userInfo.login
// })
// person = channel?.attachedTo as Ref<Person>
// }
if (person === undefined) {
// We need to create some person to identify this account.
person = await this._client.createDoc(contact.class.Person, contact.space.Contacts, {
name: userName,
avatarType: AvatarType.EXTERNAL,
avatarProps: { url: userInfo.avatarUrl },
city: '',
comments: 0,
channels: 0,
attachments: 0
})
await this._client.addCollection(
contact.class.Channel,
contact.space.Contacts,
person,
contact.class.Person,
'channels',
{
provider: contact.channelProvider.GitHub,
value: userInfo.login
}
)
if (userInfo.email != null && userInfo.email.trim() !== '') {
await this._client.addCollection(
contact.class.Channel,
contact.space.Contacts,
person,
contact.class.Person,
'channels',
{
provider: contact.channelProvider.Email,
value: userInfo.email
}
)
}
}
return person
}
// if (person === undefined) {
// // We need to create some person to identify this account.
// person = await this._client.createDoc(contact.class.Person, contact.space.Contacts, {
// name: userName,
// avatarType: AvatarType.EXTERNAL,
// avatarProps: { url: userInfo.avatarUrl },
// city: '',
// comments: 0,
// channels: 0,
// attachments: 0
// })
// await this._client.addCollection(
// contact.class.Channel,
// contact.space.Contacts,
// person,
// contact.class.Person,
// 'channels',
// {
// provider: contact.channelProvider.GitHub,
// value: userInfo.login
// }
// )
// if (userInfo.email != null && userInfo.email.trim() !== '') {
// await this._client.addCollection(
// contact.class.Channel,
// contact.space.Contacts,
// person,
// contact.class.Person,
// 'channels',
// {
// provider: contact.channelProvider.Email,
// value: userInfo.email
// }
// )
// }
// }
// return person
// }
async getGithubLogin (container: IntegrationContainer, person: Ref<Person>): Promise<UserInfo | undefined> {
const accounts = this.client.getModel().findAllSync(contact.class.PersonAccount, {})
const acc = accounts.find((it) => it.person === person && it.email.startsWith('github:'))
if (acc === undefined) {
return // Nobody, will use system account.
}
const login = acc.email.substring(7)
let info = await this.liveQuery.findOne(github.class.GithubUserInfo, { login })
if (info === undefined) {
// We need to retrieve info for login
const response: any = await container.octokit?.graphql(
`query($login: String!) {
user(login: $login) {
id
email
login
name
avatarUrl
}
}`,
{
login
}
)
info = response.user
await this._client.createDoc(github.class.GithubUserInfo, contact.space.Contacts, info as Data<GithubUserInfo>)
}
return info
// TODO: FIXME
throw new Error('Not implemented')
// const accounts = this.client.getModel().findAllSync(contact.class.PersonAccount, {})
// const acc = accounts.find((it) => it.person === person && it.email.startsWith('github:'))
// if (acc === undefined) {
// return // Nobody, will use system account.
// }
// const login = acc.email.substring(7)
// let info = await this.liveQuery.findOne(github.class.GithubUserInfo, { login })
// if (info === undefined) {
// // We need to retrieve info for login
// const response: any = await container.octokit?.graphql(
// `query($login: String!) {
// user(login: $login) {
// id
// email
// login
// name
// avatarUrl
// }
// }`,
// {
// login
// }
// )
// }
// info = response.user
// await this._client.createDoc(github.class.GithubUserInfo, contact.space.Contacts, info as Data<GithubUserInfo>)
// }
// return info
}
async syncUserData (ctx: MeasureContext, users: GithubUserRecord[]): Promise<void> {
// TODO: FIXME
throw new Error('Not implemented')
// Let's sync information about users and send some details
const accounts = await this._client.findAll(contact.class.PersonAccount, {
email: { $in: users.map((it) => `github:${it._id}`) }
})
const userAuths = await this._client.findAll(github.class.GithubAuthentication, {})
const persons = await this._client.findAll(contact.class.Person, { _id: { $in: accounts.map((it) => it.person) } })
for (const record of users) {
if (record.error !== undefined) {
// Skip accounts with error
continue
}
const account = accounts.find((it) => it.email === `github:${record._id}`)
const userAuth = userAuths.find((it) => it.login === record._id)
const person = persons.find((it) => account?.person)
if (account === undefined || userAuth === undefined || person === undefined) {
continue
}
const accountRef = record.accounts[this.workspace.name]
try {
await this.platform.checkRefreshToken(record, true)
// const accounts = await this._client.findAll(contact.class.PersonAccount, {
// email: { $in: users.map((it) => `github:${it._id}`) }
// })
// const userAuths = await this._client.findAll(github.class.GithubAuthentication, {})
// const persons = await this._client.findAll(contact.class.Person, { _id: { $in: accounts.map((it) => it.person) } })
// for (const record of users) {
// if (record.error !== undefined) {
// // Skip accounts with error
// continue
// }
// const account = accounts.find((it) => it.email === `github:${record._id}`)
// const userAuth = userAuths.find((it) => it.login === record._id)
// const person = persons.find((it) => account?.person)
// if (account === undefined || userAuth === undefined || person === undefined) {
// continue
// }
// const accountRef = record.accounts[this.workspace.name]
// try {
// await this.platform.checkRefreshToken(record, true)
const ops = new TxOperations(this.client, accountRef)
await syncUser(ctx, record, userAuth, ops, accountRef)
} catch (err: any) {
try {
await this.platform.revokeUserAuth(record)
} catch (err: any) {
ctx.error(`Failed to revoke user ${record._id}`, err)
}
if (err.response?.data?.message !== 'Bad credentials') {
ctx.error(`Failed to sync user ${record._id}`, err)
Analytics.handleError(err)
}
if (userAuth !== undefined) {
await this._client.update<GithubAuthentication>(
userAuth,
{
error: errorToObj(err)
},
undefined,
Date.now(),
accountRef
)
}
}
}
// const ops = new TxOperations(this.client, accountRef)
// await syncUser(ctx, record, userAuth, ops, accountRef)
// } catch (err: any) {
// try {
// await this.platform.revokeUserAuth(record)
// } catch (err: any) {
// ctx.error(`Failed to revoke user ${record._id}`, err)
// }
// if (err.response?.data?.message !== 'Bad credentials') {
// ctx.error(`Failed to sync user ${record._id}`, err)
// Analytics.handleError(err)
// }
// if (userAuth !== undefined) {
// await this._client.update<GithubAuthentication>(
// userAuth,
// {
// error: errorToObj(err)
// },
// undefined,
// Date.now(),
// accountRef
// )
// }
// }
// }
}
async getOctokit (account: Ref<PersonAccount>): Promise<Octokit | undefined> {
let record = await this.platform.getAccountByRef(this.workspace.name, account)
async getOctokit (account: PersonId): Promise<Octokit | undefined> {
// TODO: FIXME
throw new Error('Not implemented')
// let record = await this.platform.getAccountByRef(this.workspace.name, account)
// const accountRef = this.accounts.find((it) => it._id === account)
const [accountRef] = this.client.getModel().findAllSync(contact.class.PersonAccount, { _id: account })
if (record === undefined) {
if (accountRef !== undefined) {
const accounts = this._client.getModel().getAccountByPersonId(accountRef.person)
for (const aa of accounts) {
record = await this.platform.getAccountByRef(this.workspace.name, aa._id)
if (record !== undefined) {
break
}
}
}
}
// Check and refresh token if required.
if (record !== undefined) {
this.ctx.info('get octokit', { account, recordId: record._id, workspace: this.workspace.name })
await this.platform.checkRefreshToken(record)
return new Octokit({
auth: record.token,
client_id: config.ClientID,
client_secret: config.ClientSecret
})
}
// const [accountRef] = this.client.getModel().findAllSync(contact.class.PersonAccount, { _id: account })
// if (record === undefined) {
// if (accountRef !== undefined) {
// const accounts = this._client.getModel().getAccountByPersonId(accountRef.person)
// for (const aa of accounts) {
// record = await this.platform.getAccountByRef(this.workspace.name, aa._id)
// if (record !== undefined) {
// break
// }
// }
// }
// }
// // Check and refresh token if required.
// if (record !== undefined) {
// this.ctx.info('get octokit', { account, recordId: record._id, workspace: this.workspace.name })
// await this.platform.checkRefreshToken(record)
// return new Octokit({
// auth: record.token,
// client_id: config.ClientID,
// client_secret: config.ClientSecret
// })
// }
// We need to inform user, he need to authorize this account with github.
if (accountRef !== undefined && !this.authRequestSend.has(accountRef._id)) {
this.authRequestSend.add(accountRef._id)
const person = await this.liveQuery.findOne(contact.class.Person, { _id: accountRef.person })
if (person !== undefined) {
const personSpace = await this.liveQuery.findOne(contact.class.PersonSpace, { person: person._id })
if (personSpace !== undefined) {
// We need to remove if user has authentication in workspace but doesn't have a record.
// // We need to inform user, he need to authorize this account with github.
// if (accountRef !== undefined && !this.authRequestSend.has(accountRef._id)) {
// this.authRequestSend.add(accountRef._id)
// const person = await this.liveQuery.findOne(contact.class.Person, { _id: accountRef.person })
// if (person !== undefined) {
// const personSpace = await this.liveQuery.findOne(contact.class.PersonSpace, { person: person._id })
// if (personSpace !== undefined) {
// // We need to remove if user has authentication in workspace but doesn't have a record.
const accounts = this._client.getModel().getAccountByPersonId(accountRef.person)
const authentications = await this.liveQuery.findAll(github.class.GithubAuthentication, {
createdBy: { $in: accounts.map((it) => it._id) }
})
for (const auth of authentications) {
await this._client.remove(auth)
}
// const accounts = this._client.getModel().getAccountByPersonId(accountRef.person)
// const authentications = await this.liveQuery.findAll(github.class.GithubAuthentication, {
// createdBy: { $in: accounts.map((it) => it._id) }
// })
// for (const auth of authentications) {
// await this._client.remove(auth)
// }
await createNotification(this._client, person, {
user: account,
space: personSpace._id,
message: github.string.AuthenticatedWithGithubRequired,
props: {}
})
}
}
}
this.ctx.info('get octokit: return bot', { account, workspace: this.workspace.name })
// await createNotification(this._client, person, {
// user: account,
// space: personSpace._id,
// message: github.string.AuthenticatedWithGithubRequired,
// props: {}
// })
// }
// }
// }
// this.ctx.info('get octokit: return bot', { account, workspace: this.workspace.name })
}
async isPlatformUser (account: Ref<PersonAccount>): Promise<boolean> {
let record = await this.platform.getAccountByRef(this.workspace.name, account)
const accountRef = await this.liveQuery.findOne(contact.class.PersonAccount, { _id: account })
if (record === undefined) {
if (accountRef !== undefined) {
const accounts = this._client.getModel().getAccountByPersonId(accountRef.person)
for (const aa of accounts) {
record = await this.platform.getAccountByRef(this.workspace.name, aa._id)
if (record !== undefined) {
break
}
}
}
}
// Check and refresh token if required.
return record !== undefined && accountRef !== undefined
async isPlatformUser (account: PersonId): Promise<boolean> {
// TODO: FIXME
throw new Error('Not implemented')
// let record = await this.platform.getAccountByRef(this.workspace.name, account)
// const accountRef = await this.liveQuery.findOne(contact.class.PersonAccount, { _id: account })
// if (record === undefined) {
// if (accountRef !== undefined) {
// const accounts = this._client.getModel().getAccountByPersonId(accountRef.person)
// for (const aa of accounts) {
// record = await this.platform.getAccountByRef(this.workspace.name, aa._id)
// if (record !== undefined) {
// break
// }
// }
// }
// }
// // Check and refresh token if required.
// return record !== undefined && accountRef !== undefined
}
async uploadFile (patch: string, file?: string, contentType?: string): Promise<Blob | undefined> {
const id: string = file ?? generateId()
await this.storageAdapter.put(this.ctx, this.workspace, id, patch, contentType ?? 'text/x-patch')
return await this.storageAdapter.stat(this.ctx, this.workspace, id)
const dataId = this.workspace.dataId ?? (this.workspace.uuid as unknown as WorkspaceDataId)
await this.storageAdapter.put(this.ctx, dataId, id, patch, contentType ?? 'text/x-patch')
return await this.storageAdapter.stat(this.ctx, dataId, id)
}
integrationRepositories: WithLookup<GithubIntegrationRepository>[] = []
@@ -729,7 +753,7 @@ export class GithubWorker implements IntegrationManager {
this.updateRequests = 1
this.syncPromise = this.syncAndWait()
const userRecords = await this.platform.getUsers(this.workspace.name)
const userRecords = await this.platform.getUsers(this.workspace.uuid)
try {
await this.syncUserData(this.ctx, userRecords)
} catch (err: any) {
@@ -850,57 +874,59 @@ export class GithubWorker implements IntegrationManager {
}
private async queryAccounts (): Promise<void> {
const updateAccounts = async (accounts: PersonAccount[]): Promise<void> => {
const persons = await this.liveQuery.findAll(contact.class.Person, {
_id: { $in: accounts.map((it) => it.person) }
})
const h = this.client.getHierarchy()
for (const a of accounts) {
if (a.email.startsWith('github:')) {
const login = a.email.substring(7)
const person = persons.find((it) => it._id === a.person)
if (person !== undefined) {
// #1 check if person has GithubUser mixin.
if (!h.hasMixin(person, github.mixin.GithubUser)) {
await this._client.createMixin(person._id, person._class, person.space, github.mixin.GithubUser, {
url: `https://github.com/${login}`
})
} else {
const ghu = h.as(person, github.mixin.GithubUser)
if (ghu.url !== `https://github.com/${login}`) {
await this._client.updateMixin(person._id, person._class, person.space, github.mixin.GithubUser, {
url: `https://github.com/${login}`
})
}
}
// #2 check if person has contact github and if not add it.
const channel = await this._client.findOne(contact.class.Channel, {
provider: contact.channelProvider.GitHub,
value: login,
attachedTo: person._id
})
if (channel === undefined) {
await this._client.addCollection(
contact.class.Channel,
person.space,
person._id,
contact.class.Person,
'channels',
{
provider: contact.channelProvider.GitHub,
value: login
}
)
}
}
}
}
}
await new Promise<void>((resolve, reject) => {
this.liveQuery.query(contact.class.PersonAccount, {}, (res) => {
void updateAccounts(res).then(resolve).catch(reject)
})
})
// TODO: FIXME
throw new Error('Not implemented')
// const updateAccounts = async (accounts: PersonAccount[]): Promise<void> => {
// const persons = await this.liveQuery.findAll(contact.class.Person, {
// _id: { $in: accounts.map((it) => it.person) }
// })
// const h = this.client.getHierarchy()
// for (const a of accounts) {
// if (a.email.startsWith('github:')) {
// const login = a.email.substring(7)
// const person = persons.find((it) => it._id === a.person)
// if (person !== undefined) {
// // #1 check if person has GithubUser mixin.
// if (!h.hasMixin(person, github.mixin.GithubUser)) {
// await this._client.createMixin(person._id, person._class, person.space, github.mixin.GithubUser, {
// url: `https://github.com/${login}`
// })
// } else {
// const ghu = h.as(person, github.mixin.GithubUser)
// if (ghu.url !== `https://github.com/${login}`) {
// await this._client.updateMixin(person._id, person._class, person.space, github.mixin.GithubUser, {
// url: `https://github.com/${login}`
// })
// }
// }
// // #2 check if person has contact github and if not add it.
// const channel = await this._client.findOne(contact.class.Channel, {
// provider: contact.channelProvider.GitHub,
// value: login,
// attachedTo: person._id
// })
// if (channel === undefined) {
// await this._client.addCollection(
// contact.class.Channel,
// person.space,
// person._id,
// contact.class.Person,
// 'channels',
// {
// provider: contact.channelProvider.GitHub,
// value: login
// }
// )
// }
// }
// }
// }
// }
// await new Promise<void>((resolve, reject) => {
// this.liveQuery.query(contact.class.PersonAccount, {}, (res) => {
// void updateAccounts(res).then(resolve).catch(reject)
// })
// })
}
async performExternalSync (
@@ -960,7 +986,7 @@ export class GithubWorker implements IntegrationManager {
field,
version,
docs: docs.length,
workspace: this.workspace.name
workspace: this.workspace.uuid
})
const byClass = this.groupByClass(docs)
@@ -1128,7 +1154,7 @@ export class GithubWorker implements IntegrationManager {
if (!hadExternalChanges && !hadSyncChanges && !hadDerivedChanges) {
if (this.previousWait !== 0) {
this.ctx.info('Wait for changes:', { previousWait: this.previousWait, workspace: this.workspace.name })
this.ctx.info('Wait for changes:', { previousWait: this.previousWait, workspace: this.workspace.uuid })
this.previousWait = 0
}
// Wait until some sync documents will be modified, updated.
@@ -1170,7 +1196,7 @@ export class GithubWorker implements IntegrationManager {
if (docs.length > 0) {
this.previousWait += docs.length
this.ctx.info('Syncing', { docs: docs.length, workspace: this.workspace.name })
this.ctx.info('Syncing', { docs: docs.length, workspace: this.workspace.uuid })
const bySpace = groupByArray(docs, (it) => it.space)
for (const [k, v] of bySpace.entries()) {
@@ -1232,7 +1258,7 @@ export class GithubWorker implements IntegrationManager {
}
async checkMapping (): Promise<void> {
for (const intgr of this.platform.integrations.filter((it) => it.workspace === this.workspace.name)) {
for (const intgr of this.platform.integrations.filter((it) => it.workspace === this.workspace.uuid)) {
const integration = await this._client.findOne(github.class.GithubIntegration, {
installationId: intgr.installationId
})
@@ -1298,7 +1324,7 @@ export class GithubWorker implements IntegrationManager {
const existing = externalDocs.find((it) => it._id === info._id)
const mapper = this.mappers.find((it) => it._class.includes(info.objectClass))?.mapper
if (mapper === undefined) {
this.ctx.info('No mapper for class', { objectClass: info.objectClass, workspace: this.workspace.name })
this.ctx.info('No mapper for class', { objectClass: info.objectClass, workspace: this.workspace.uuid })
await derivedClient.update<DocSyncInfo>(info, {
needSync: githubSyncVersion
})
@@ -1371,7 +1397,7 @@ export class GithubWorker implements IntegrationManager {
'sync doc',
{},
(ctx) => mapper.sync(existing, info, parent, derivedClient),
{ url: info.url.toLowerCase(), workspace: this.workspace.name }
{ url: info.url.toLowerCase(), workspace: this.workspace.uuid }
)
if (docUpdate !== undefined) {
await derivedClient.update(info, docUpdate)
@@ -1398,7 +1424,7 @@ export class GithubWorker implements IntegrationManager {
this.ctx.info('Trigger check pending:', {
requests: this.triggerRequests,
updates: this.updateRequests,
workspace: this.workspace.name
workspace: this.workspace.uuid
})
this.triggerRequests = 0
return
@@ -1413,7 +1439,7 @@ export class GithubWorker implements IntegrationManager {
triggerTimeout = setTimeout(() => {
triggerTimeout = undefined
if (was0) {
this.ctx.info('Sync triggered', { request: this.triggerRequests, workspace: this.workspace.name })
this.ctx.info('Sync triggered', { request: this.triggerRequests, workspace: this.workspace.uuid })
}
resolve()
}, 50) // Small timeout to aggregate few bulk changes.
@@ -1427,7 +1453,7 @@ export class GithubWorker implements IntegrationManager {
updateTimeout = setTimeout(() => {
updateTimeout = undefined
if (was0) {
this.ctx.info('Sync update triggered', { requests: this.updateRequests, workspace: this.workspace.name })
this.ctx.info('Sync update triggered', { requests: this.updateRequests, workspace: this.workspace.uuid })
}
resolve()
}, 50) // Small timeout to aggregate few bulk changes.
@@ -1452,7 +1478,7 @@ export class GithubWorker implements IntegrationManager {
}
await this.ctx.withLog(
'external sync',
{ installation: integration.installationName, workspace: this.workspace.name },
{ installation: integration.installationName, workspace: this.workspace.uuid },
async () => {
const enabled = integration.enabled && integration.octokit !== undefined
@@ -1515,7 +1541,7 @@ export class GithubWorker implements IntegrationManager {
}
await this.ctx.withLog(
'external sync',
{ _class: _class.join(', '), workspace: this.workspace.name },
{ _class: _class.join(', '), workspace: this.workspace.uuid },
async () => {
await mapper.externalFullSync(integration, derivedClient, _projects, _repositories)
}
@@ -1565,18 +1591,18 @@ export class GithubWorker implements IntegrationManager {
platformWorker: PlatformWorker,
ctx: MeasureContext,
installations: Map<number, InstallationRecord>,
workspace: WorkspaceIdWithUrl,
workspace: WorkspaceIds,
branding: Branding | null,
app: App,
storageAdapter: StorageAdapter,
reconnect: (workspaceId: string, event: ClientConnectEvent) => void
): Promise<GithubWorker | undefined> {
ctx.info('Connecting to', { workspace: workspace.workspaceUrl, workspaceId: workspace.workspaceName })
ctx.info('Connecting to', { workspace })
let client: Client | undefined
let endpoint: string | undefined
try {
;({ client, endpoint } = await createPlatformClient(workspace.name, 30000, async (event: ClientConnectEvent) => {
reconnect(workspace.name, event)
;({ client, endpoint } = await createPlatformClient(workspace.uuid, 30000, async (event: ClientConnectEvent) => {
reconnect(workspace.uuid, event)
}))
await GithubWorker.checkIntegrations(client, installations)
@@ -1592,14 +1618,8 @@ export class GithubWorker implements IntegrationManager {
workspace,
branding
)
ctx.info('Init worker', { workspace: workspace.workspaceUrl, workspaceId: workspace.workspaceName })
void worker.init().catch((err) => {
ctx.error('Failed to init worker', {
workspace: workspace.workspaceUrl,
workspaceId: workspace.workspaceName,
error: err
})
})
ctx.info('Init worker', { workspace: workspace.url, workspaceId: workspace.uuid })
void worker.init()
return worker
} catch (err: any) {
ctx.error('timeout during to connect', { workspace, error: err })
@@ -1624,7 +1644,7 @@ export async function syncUser (
record: GithubUserRecord,
userAuth: WithLookup<GithubAuthentication>,
client: TxOperations,
account: Ref<Account>
account: PersonId
): Promise<void> {
const okit = new Octokit({
auth: record.token,