Skip to content

Commit

Permalink
Merge branch 'develop' into feat/password-validation
Browse files Browse the repository at this point in the history
Signed-off-by: Dakshesh Jain <[email protected]>
  • Loading branch information
dakshesh14 authored Jan 22, 2025
2 parents 2854169 + 86da890 commit 2a6a28e
Show file tree
Hide file tree
Showing 122 changed files with 2,035 additions and 815 deletions.
210 changes: 131 additions & 79 deletions common/config/rush/pnpm-lock.yaml

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions dev/base-image/print.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
FROM hardcoreeng/base

# Chromium hangs when usging LD_PRELOAD and MALLOC_CONF
ENV LD_PRELOAD=
ENV MALLOC_CONF=

# We don't need the standalone Chromium
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
# Set executable path for puppeteer
Expand Down
202 changes: 202 additions & 0 deletions dev/tool/src/account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import {
type Account,
type AccountDB,
changeEmail,
getAccount,
listWorkspacesPure,
type Workspace
} from '@hcengineering/account'
import core, { getWorkspaceId, type MeasureContext, systemAccountEmail, TxOperations } from '@hcengineering/core'
import contact from '@hcengineering/model-contact'
import { getTransactorEndpoint } from '@hcengineering/server-client'
import { generateToken } from '@hcengineering/server-token'
import { connect } from '@hcengineering/server-tool'

export async function renameAccount (
ctx: MeasureContext,
db: AccountDB,
accountsUrl: string,
oldEmail: string,
newEmail: string
): Promise<void> {
const account = await getAccount(db, oldEmail)
if (account == null) {
throw new Error("Account does'n exists")
}

const newAccount = await getAccount(db, newEmail)
if (newAccount != null) {
throw new Error('New Account email already exists:' + newAccount?.email + ' ' + newAccount?._id?.toString())
}

await changeEmail(ctx, db, account, newEmail)

await fixWorkspaceEmails(account, db, accountsUrl, oldEmail, newEmail)
}

export async function fixAccountEmails (
ctx: MeasureContext,
db: AccountDB,
transactorUrl: string,
oldEmail: string,
newEmail: string
): Promise<void> {
const account = await getAccount(db, newEmail)
if (account == null) {
throw new Error("Account does'n exists")
}

await fixWorkspaceEmails(account, db, transactorUrl, oldEmail, newEmail)
}
async function fixWorkspaceEmails (
account: Account,
db: AccountDB,
accountsUrl: string,
oldEmail: string,
newEmail: string
): Promise<void> {
const accountWorkspaces = account.workspaces.map((it) => it.toString())
// We need to update all workspaces
const workspaces = await listWorkspacesPure(db)
for (const ws of workspaces) {
if (!accountWorkspaces.includes(ws._id.toString())) {
continue
}
console.log('checking workspace', ws.workspaceName, ws.workspace)

const wsid = getWorkspaceId(ws.workspace)
const endpoint = await getTransactorEndpoint(generateToken(systemAccountEmail, wsid))

// Let's connect and update account information.
await fixEmailInWorkspace(endpoint, ws, oldEmail, newEmail)
}
}

async function fixEmailInWorkspace (
transactorUrl: string,
ws: Workspace,
oldEmail: string,
newEmail: string
): Promise<void> {
const connection = await connect(transactorUrl, { name: ws.workspace }, undefined, {
mode: 'backup',
model: 'upgrade', // Required for force all clients reload after operation will be complete.
admin: 'true'
})
try {
const personAccount = await connection.findOne(contact.class.PersonAccount, { email: oldEmail })

if (personAccount !== undefined) {
console.log('update account in ', ws.workspace)
const ops = new TxOperations(connection, core.account.ConfigUser)
await ops.update(personAccount, { email: newEmail })
}
} catch (err: any) {
console.error(err)
} finally {
await connection.close()
}
}

interface GithubUserResult {
login: string | null
code: number
rateLimitReset?: number | null
}

async function getGithubUser (githubId: string, ghToken?: string): Promise<GithubUserResult> {
const options =
ghToken !== undefined
? {
headers: {
Authorization: `Bearer ${ghToken}`,
Accept: 'application/vnd.github.v3+json'
}
}
: undefined
const res = await fetch(`https://api.github.com/user/${githubId}`, options)

if (res.status === 200) {
return {
login: (await res.json()).login,
code: 200
}
}

if (res.status === 403) {
const rateLimitReset = res.headers.get('X-RateLimit-Reset')
return {
login: null,
code: res.status,
rateLimitReset: rateLimitReset != null ? parseInt(rateLimitReset) * 1000 : null
}
}

return {
login: null,
code: res.status
}
}

export async function fillGithubUsers (ctx: MeasureContext, db: AccountDB, ghToken?: string): Promise<void> {
const githubAccounts = await db.account.find({ githubId: { $ne: null } })
if (githubAccounts.length === 0) {
ctx.info('no github accounts found')
return
}

const accountsToProcess = githubAccounts.filter(({ githubId, githubUser }) => githubUser == null && githubId != null)
if (accountsToProcess.length === 0) {
ctx.info('no github accounts left to fill')
return
}

ctx.info('processing github accounts', { total: accountsToProcess.length })
const defaultRetryTimeout = 1000 * 60 * 5 // 5 minutes
let processed = 0
for (const account of accountsToProcess) {
while (true) {
try {
if (account.githubId == null) break
let username: string | undefined
if (account.email.startsWith('github:')) {
username = account.email.slice(7)
} else {
const githubUserRes = await getGithubUser(account.githubId, ghToken)
if (githubUserRes.code === 200 && githubUserRes.login != null) {
username = githubUserRes.login
} else if (githubUserRes.code === 404) {
ctx.info('github user not found', { githubId: account.githubId })
break
} else if (githubUserRes.code === 403) {
const timeout =
githubUserRes.rateLimitReset != null
? githubUserRes.rateLimitReset - Date.now() + 1000
: defaultRetryTimeout
ctx.info('rate limit exceeded. Retrying in ', {
githubId: account.githubId,
retryTimeoutMin: Math.ceil(timeout / (1000 * 60))
})
await new Promise((resolve) => setTimeout(resolve, timeout))
} else {
ctx.error('failed to get github user', { githubId: account.githubId, ...githubUserRes })
break
}
}
if (username != null) {
await db.account.updateOne({ _id: account._id }, { githubUser: username.toLowerCase() })
ctx.info('github user added', { githubId: account.githubId, githubUser: username.toLowerCase() })
break
}
} catch (err: any) {
ctx.error('failed to fill github user', { githubId: account.githubId, err })
break
}
}
processed++
if (processed % 100 === 0) {
ctx.info('processing accounts:', { processed, of: accountsToProcess.length })
}
}
ctx.info('finished processing accounts:', { processed, of: accountsToProcess.length })
}
33 changes: 33 additions & 0 deletions dev/tool/src/fulltext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// Copyright © 2025 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 MeasureContext } from '@hcengineering/core'

export async function reindexWorkspace (ctx: MeasureContext, fulltextUrl: string, token: string): Promise<void> {
try {
const res = await fetch(fulltextUrl + '/api/v1/reindex', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ token })
})
if (!res.ok) {
throw new Error(`HTTP Error ${res.status} ${res.statusText}`)
}
} catch (err: any) {
ctx.error('failed to reset index', { err })
}
}
Loading

0 comments on commit 2a6a28e

Please sign in to comment.