UBERF-6984: Host-based branding (#5657)
Signed-off-by: Alexey Zinoviev <alexey.zinoviev@xored.com>
@ -85,9 +85,6 @@ services:
|
||||
- COLLABORATOR_URL=ws://localhost:3078
|
||||
- COLLABORATOR_API_URL=http://localhost:3078
|
||||
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
||||
- TITLE=DevPlatform
|
||||
- DEFAULT_LANGUAGE=ru
|
||||
- LAST_NAME_FIRST=true
|
||||
restart: unless-stopped
|
||||
collaborator:
|
||||
image: hardcoreeng/collaborator
|
||||
|
58
dev/prod/public/branding.json
Normal file
@ -0,0 +1,58 @@
|
||||
{
|
||||
"localhost:8080": {
|
||||
"title": "Platform",
|
||||
"languages": "en,ru,pt,es",
|
||||
"defaultLanguage": "en",
|
||||
"defaultApplication": "tracker",
|
||||
"defaultSpace": "tracker:project:DefaultProject",
|
||||
"defaultSpecial": "issues",
|
||||
"links": [
|
||||
{
|
||||
"rel": "manifest",
|
||||
"href": "/platform/site.webmanifest"
|
||||
},
|
||||
{
|
||||
"rel": "icon",
|
||||
"href": "/platform/favicon.svg",
|
||||
"type": "image/svg+xml"
|
||||
},
|
||||
{
|
||||
"rel": "shortcut icon",
|
||||
"href": "/platform/favicon.ico",
|
||||
"sizes": "any"
|
||||
},
|
||||
{
|
||||
"rel": "apple-touch-icon",
|
||||
"href": "/platform/icon-192.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
"localhost:8087": {
|
||||
"title": "DevPlatform",
|
||||
"languages": "en,ru,pt,es",
|
||||
"defaultLanguage": "en",
|
||||
"defaultApplication": "tracker",
|
||||
"defaultSpace": "tracker:project:DefaultProject",
|
||||
"defaultSpecial": "issues",
|
||||
"links": [
|
||||
{
|
||||
"rel": "manifest",
|
||||
"href": "/platform/site.webmanifest"
|
||||
},
|
||||
{
|
||||
"rel": "icon",
|
||||
"href": "/platform/favicon.svg",
|
||||
"type": "image/svg+xml"
|
||||
},
|
||||
{
|
||||
"rel": "shortcut icon",
|
||||
"href": "/platform/favicon.ico",
|
||||
"sizes": "any"
|
||||
},
|
||||
{
|
||||
"rel": "apple-touch-icon",
|
||||
"href": "/platform/icon-192.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -7,6 +7,5 @@
|
||||
"CALENDAR_URL": "http://localhost:8095",
|
||||
"REKONI_URL": "http://localhost:4004",
|
||||
"COLLABORATOR_URL": "ws://localhost:3078",
|
||||
"COLLABORATOR_API_URL": "http://localhost:3078",
|
||||
"LAST_NAME_FIRST": "true"
|
||||
"COLLABORATOR_API_URL": "http://localhost:3078"
|
||||
}
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 961 B After Width: | Height: | Size: 961 B |
Before Width: | Height: | Size: 930 KiB After Width: | Height: | Size: 930 KiB |
Before Width: | Height: | Size: 2.3 MiB After Width: | Height: | Size: 2.3 MiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 221 KiB After Width: | Height: | Size: 221 KiB |
@ -3,13 +3,8 @@
|
||||
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
|
||||
<title>Platform</title>
|
||||
|
||||
<link rel="manifest" href="/site.webmanifest">
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||
<link rel="shortcut icon" href="/favicon.ico" sizes="any">
|
||||
<link rel="apple-touch-icon" href="/icon-192.png">
|
||||
<link rel="shortcut icon" href="/platform/favicon.ico" sizes="any" id="default-favicon">
|
||||
</head>
|
||||
|
||||
<body style="margin: 0; overflow: hidden;">
|
||||
|
@ -101,12 +101,28 @@ interface Config {
|
||||
COLLABORATOR_URL: string
|
||||
COLLABORATOR_API_URL: string
|
||||
PUSH_PUBLIC_KEY: string
|
||||
TITLE?: string
|
||||
LANGUAGES?: string
|
||||
DEFAULT_LANGUAGE?: string
|
||||
LAST_NAME_FIRST?: string
|
||||
BRANDING_URL?: string
|
||||
}
|
||||
|
||||
export interface Branding {
|
||||
title?: string
|
||||
links?: {
|
||||
rel: string
|
||||
href: string
|
||||
type?: string
|
||||
sizes?: string
|
||||
}[]
|
||||
languages?: string
|
||||
lastNameFirst?: string
|
||||
defaultLanguage?: string
|
||||
defaultApplication?: string
|
||||
defaultSpace?: string
|
||||
defaultSpecial?: string
|
||||
initWorkspace?: string
|
||||
}
|
||||
|
||||
export type BrandingMap = Record<string, Branding>
|
||||
|
||||
const devConfig = process.env.CLIENT_TYPE === 'dev-production'
|
||||
|
||||
function configureI18n(): void {
|
||||
@ -164,7 +180,40 @@ export async function configurePlatform() {
|
||||
configureI18n()
|
||||
|
||||
const config: Config = await (await fetch(devConfig? '/config-dev.json' : '/config.json')).json()
|
||||
const branding: BrandingMap = await (await fetch(config.BRANDING_URL ?? '/branding.json')).json()
|
||||
const myBranding = branding[window.location.host] ?? {}
|
||||
|
||||
console.log('loading configuration', config)
|
||||
console.log('loaded branding', myBranding)
|
||||
|
||||
const title = myBranding.title ?? 'Platform'
|
||||
|
||||
// apply branding
|
||||
window.document.title = title
|
||||
|
||||
const links = myBranding.links ?? []
|
||||
if (links.length > 0) {
|
||||
// remove the default favicon
|
||||
// it's only needed for Safari which cannot use dynamically added links for favicons
|
||||
document.getElementById('default-favicon')?.remove()
|
||||
|
||||
for (const link of links) {
|
||||
const htmlLink = document.createElement('link')
|
||||
htmlLink.rel = link.rel
|
||||
htmlLink.href = link.href
|
||||
|
||||
if (link.type !== undefined) {
|
||||
htmlLink.type = link.type
|
||||
}
|
||||
|
||||
if (link.sizes !== undefined) {
|
||||
htmlLink.setAttribute('sizes', link.sizes)
|
||||
}
|
||||
|
||||
document.head.appendChild(htmlLink)
|
||||
}
|
||||
}
|
||||
|
||||
setMetadata(login.metadata.AccountsUrl, config.ACCOUNTS_URL)
|
||||
setMetadata(presentation.metadata.UploadURL, config.UPLOAD_URL)
|
||||
setMetadata(presentation.metadata.CollaboratorUrl, config.COLLABORATOR_URL)
|
||||
@ -187,8 +236,8 @@ export async function configurePlatform() {
|
||||
|
||||
setMetadata(uiPlugin.metadata.DefaultApplication, login.component.LoginApp)
|
||||
|
||||
setMetadata(contactPlugin.metadata.LastNameFirst, config.LAST_NAME_FIRST === 'true' ?? false)
|
||||
const languages = config.LANGUAGES ? (config.LANGUAGES as string).split(',').map((l) => l.trim()) : ['en', 'ru', 'es', 'pt']
|
||||
setMetadata(contactPlugin.metadata.LastNameFirst, myBranding.lastNameFirst === 'true' ?? false)
|
||||
const languages = myBranding.languages ? (myBranding.languages as string).split(',').map((l) => l.trim()) : ['en', 'ru', 'es', 'pt']
|
||||
|
||||
setMetadata(uiPlugin.metadata.Languages, languages)
|
||||
setMetadata(
|
||||
@ -245,10 +294,10 @@ export async function configurePlatform() {
|
||||
// Disable for now, since it causes performance issues on linux/docker/kubernetes boxes for now.
|
||||
setMetadata(client.metadata.UseProtocolCompression, true)
|
||||
|
||||
setMetadata(uiPlugin.metadata.PlatformTitle, config.TITLE ?? 'Platform')
|
||||
setMetadata(workbench.metadata.PlatformTitle, config.TITLE ?? 'Platform')
|
||||
setDefaultLanguage(config.DEFAULT_LANGUAGE ?? 'en')
|
||||
setMetadata(workbench.metadata.DefaultApplication, 'tracker')
|
||||
setMetadata(workbench.metadata.DefaultSpace, tracker.project.DefaultProject)
|
||||
setMetadata(workbench.metadata.DefaultSpecial, 'issues')
|
||||
setMetadata(uiPlugin.metadata.PlatformTitle, title)
|
||||
setMetadata(workbench.metadata.PlatformTitle, title)
|
||||
setDefaultLanguage(myBranding.defaultLanguage ?? 'en')
|
||||
setMetadata(workbench.metadata.DefaultApplication, myBranding.defaultApplication ?? 'tracker')
|
||||
setMetadata(workbench.metadata.DefaultSpace, myBranding.defaultSpace ?? tracker.project.DefaultProject)
|
||||
setMetadata(workbench.metadata.DefaultSpecial, myBranding.defaultSpecial ?? 'issues')
|
||||
}
|
||||
|
@ -70,6 +70,7 @@ export async function checkOrphanWorkspaces (
|
||||
db,
|
||||
client,
|
||||
productId,
|
||||
null,
|
||||
ws.workspace,
|
||||
storageAdapter
|
||||
)
|
||||
|
@ -183,7 +183,7 @@ export function devTool (
|
||||
const { mongodbUri } = prepareTools()
|
||||
await withDatabase(mongodbUri, async (db) => {
|
||||
console.log(`creating account ${cmd.first as string} ${cmd.last as string} (${email})...`)
|
||||
await createAcc(toolCtx, db, productId, email, cmd.password, cmd.first, cmd.last, true)
|
||||
await createAcc(toolCtx, db, productId, null, email, cmd.password, cmd.first, cmd.last, true)
|
||||
})
|
||||
})
|
||||
|
||||
@ -234,7 +234,7 @@ export function devTool (
|
||||
}
|
||||
console.log('assigning to workspace', workspaceInfo)
|
||||
try {
|
||||
await assignWorkspace(toolCtx, db, productId, email, workspaceInfo.workspace, AccountRole.User)
|
||||
await assignWorkspace(toolCtx, db, productId, null, email, workspaceInfo.workspace, AccountRole.User)
|
||||
} catch (err: any) {
|
||||
console.error(err)
|
||||
}
|
||||
@ -281,7 +281,8 @@ export function devTool (
|
||||
.description('create workspace')
|
||||
.requiredOption('-w, --workspaceName <workspaceName>', 'Workspace name')
|
||||
.option('-e, --email <email>', 'Author email', 'platform@email.com')
|
||||
.action(async (workspace, cmd) => {
|
||||
.option('-i, --init <ws>', 'Init from workspace')
|
||||
.action(async (workspace, cmd: { email: string, workspaceName: string, init?: string }) => {
|
||||
const { mongodbUri, txes, version, migrateOperations } = prepareTools()
|
||||
await withDatabase(mongodbUri, async (db) => {
|
||||
await createWorkspace(
|
||||
@ -291,6 +292,7 @@ export function devTool (
|
||||
migrateOperations,
|
||||
db,
|
||||
productId,
|
||||
cmd.init !== undefined ? { initWorkspace: cmd.init } : null,
|
||||
cmd.email,
|
||||
cmd.workspaceName,
|
||||
workspace
|
||||
@ -429,9 +431,9 @@ export function devTool (
|
||||
return
|
||||
}
|
||||
if (cmd.full) {
|
||||
await dropWorkspaceFull(toolCtx, db, client, productId, workspace, storageAdapter)
|
||||
await dropWorkspaceFull(toolCtx, db, client, productId, null, workspace, storageAdapter)
|
||||
} else {
|
||||
await dropWorkspace(toolCtx, db, productId, workspace)
|
||||
await dropWorkspace(toolCtx, db, productId, null, workspace)
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -447,9 +449,9 @@ export function devTool (
|
||||
await withDatabase(mongodbUri, async (db, client) => {
|
||||
for (const workspace of await listWorkspacesByAccount(db, productId, email)) {
|
||||
if (cmd.full) {
|
||||
await dropWorkspaceFull(toolCtx, db, client, productId, workspace.workspace, storageAdapter)
|
||||
await dropWorkspaceFull(toolCtx, db, client, productId, null, workspace.workspace, storageAdapter)
|
||||
} else {
|
||||
await dropWorkspace(toolCtx, db, productId, workspace.workspace)
|
||||
await dropWorkspace(toolCtx, db, productId, null, workspace.workspace)
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -480,7 +482,7 @@ export function devTool (
|
||||
for (const ws of workspacesJSON) {
|
||||
const lastVisit = Math.floor((Date.now() - ws.lastVisit) / 1000 / 3600 / 24)
|
||||
if (lastVisit > 30) {
|
||||
await dropWorkspaceFull(toolCtx, db, client, productId, ws.workspace, storageAdapter)
|
||||
await dropWorkspaceFull(toolCtx, db, client, productId, null, ws.workspace, storageAdapter)
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -575,7 +577,7 @@ export function devTool (
|
||||
.action(async (email: string, cmd) => {
|
||||
const { mongodbUri } = prepareTools()
|
||||
await withDatabase(mongodbUri, async (db) => {
|
||||
await dropAccount(toolCtx, db, productId, email)
|
||||
await dropAccount(toolCtx, db, productId, null, email)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -13,6 +13,8 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import fs from 'fs'
|
||||
import { type BrandingMap } from '@hcengineering/account'
|
||||
import { serveAccount } from '@hcengineering/account-service'
|
||||
import { MeasureMetricsContext, newMetrics, type Tx } from '@hcengineering/core'
|
||||
import builder, { getModelVersion, migrateOperations } from '@hcengineering/model-all'
|
||||
@ -24,4 +26,11 @@ const txes = JSON.parse(JSON.stringify(builder(enabled, disabled).getTxes())) as
|
||||
|
||||
const metricsContext = new MeasureMetricsContext('account', {}, {}, newMetrics())
|
||||
|
||||
serveAccount(metricsContext, getModelVersion(), txes, migrateOperations, '')
|
||||
const brandingPath = process.env.BRANDING_PATH
|
||||
|
||||
let brandings: BrandingMap = {}
|
||||
if (brandingPath !== undefined && brandingPath !== '') {
|
||||
brandings = JSON.parse(fs.readFileSync(brandingPath, 'utf8'))
|
||||
}
|
||||
|
||||
serveAccount(metricsContext, getModelVersion(), txes, migrateOperations, '', brandings)
|
||||
|
@ -47,14 +47,24 @@ export function registerGithub (
|
||||
if (email !== undefined) {
|
||||
try {
|
||||
if (ctx.query?.state != null) {
|
||||
const loginInfo = await joinWithProvider(measureCtx, db, productId, email, first, last, ctx.query.state, {
|
||||
githubId: ctx.state.user.id
|
||||
})
|
||||
const loginInfo = await joinWithProvider(
|
||||
measureCtx,
|
||||
db,
|
||||
productId,
|
||||
null,
|
||||
email,
|
||||
first,
|
||||
last,
|
||||
ctx.query.state,
|
||||
{
|
||||
githubId: ctx.state.user.id
|
||||
}
|
||||
)
|
||||
if (ctx.session != null) {
|
||||
ctx.session.loginInfo = loginInfo
|
||||
}
|
||||
} else {
|
||||
const loginInfo = await loginWithProvider(measureCtx, db, productId, email, first, last, {
|
||||
const loginInfo = await loginWithProvider(measureCtx, db, productId, null, email, first, last, {
|
||||
githubId: ctx.state.user.id
|
||||
})
|
||||
if (ctx.session != null) {
|
||||
|
@ -48,12 +48,21 @@ export function registerGoogle (
|
||||
if (email !== undefined) {
|
||||
try {
|
||||
if (ctx.query?.state != null) {
|
||||
const loginInfo = await joinWithProvider(measureCtx, db, productId, email, first, last, ctx.query.state)
|
||||
const loginInfo = await joinWithProvider(
|
||||
measureCtx,
|
||||
db,
|
||||
productId,
|
||||
null,
|
||||
email,
|
||||
first,
|
||||
last,
|
||||
ctx.query.state
|
||||
)
|
||||
if (ctx.session != null) {
|
||||
ctx.session.loginInfo = loginInfo
|
||||
}
|
||||
} else {
|
||||
const loginInfo = await loginWithProvider(measureCtx, db, productId, email, first, last)
|
||||
const loginInfo = await loginWithProvider(measureCtx, db, productId, null, email, first, last)
|
||||
if (ctx.session != null) {
|
||||
ctx.session.loginInfo = loginInfo
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ ENV NODE_ENV production
|
||||
|
||||
WORKDIR /app
|
||||
RUN npm install --ignore-scripts=false --verbose sharp@v0.32.6 bufferutil utf-8-validate @mongodb-js/zstd --unsafe-perm
|
||||
|
||||
COPY bundle/bundle.js ./
|
||||
COPY dist/ ./dist/
|
||||
|
||||
|
@ -7,7 +7,8 @@ import account, {
|
||||
UpgradeWorker,
|
||||
accountId,
|
||||
cleanInProgressWorkspaces,
|
||||
getMethods
|
||||
getMethods,
|
||||
type BrandingMap
|
||||
} from '@hcengineering/account'
|
||||
import accountEn from '@hcengineering/account/lang/en.json'
|
||||
import accountRu from '@hcengineering/account/lang/ru.json'
|
||||
@ -34,8 +35,10 @@ export function serveAccount (
|
||||
txes: Tx[],
|
||||
migrateOperations: [string, MigrateOperation][],
|
||||
productId: string,
|
||||
brandings: BrandingMap,
|
||||
onClose?: () => void
|
||||
): void {
|
||||
console.log('Starting account service with brandings: ', brandings)
|
||||
const methods = getMethods(version, txes, migrateOperations)
|
||||
const ACCOUNT_PORT = parseInt(process.env.ACCOUNT_PORT ?? '3000')
|
||||
const dbUri = process.env.MONGO_URL
|
||||
@ -141,7 +144,14 @@ export function serveAccount (
|
||||
client = await client
|
||||
}
|
||||
const db = client.db(ACCOUNT_DB)
|
||||
const result = await method(measureCtx, db, productId, request, token)
|
||||
|
||||
let host: string | undefined
|
||||
const origin = ctx.request.headers.origin ?? ctx.request.headers.referer
|
||||
if (origin !== undefined) {
|
||||
host = new URL(origin).host
|
||||
}
|
||||
const branding = host !== undefined ? brandings[host] : null
|
||||
const result = await method(measureCtx, db, productId, branding, request, token)
|
||||
|
||||
worker?.updateResponseStatistics(result)
|
||||
ctx.body = result
|
||||
|
@ -51,7 +51,7 @@ describe('server', () => {
|
||||
params: [workspace, 'ООО Рога и Копыта']
|
||||
}
|
||||
|
||||
const result = await methods.createWorkspace(metricsContext, db, '', request)
|
||||
const result = await methods.createWorkspace(metricsContext, db, '', null, request)
|
||||
expect(result.result).toBeDefined()
|
||||
workspace = result.result as string
|
||||
})
|
||||
@ -62,12 +62,12 @@ describe('server', () => {
|
||||
params: ['andrey2', '123']
|
||||
}
|
||||
|
||||
const result = await methods.createAccount(metricsContext, db, '', request)
|
||||
const result = await methods.createAccount(metricsContext, db, '', null, request)
|
||||
expect(result.result).toBeDefined()
|
||||
})
|
||||
|
||||
it('should not create, duplicate account', async () => {
|
||||
await methods.createAccount(metricsContext, db, '', {
|
||||
await methods.createAccount(metricsContext, db, '', null, {
|
||||
method: 'createAccount',
|
||||
params: ['andrey', '123']
|
||||
})
|
||||
@ -77,20 +77,20 @@ describe('server', () => {
|
||||
params: ['andrey', '123']
|
||||
}
|
||||
|
||||
const result = await methods.createAccount(metricsContext, db, '', request)
|
||||
const result = await methods.createAccount(metricsContext, db, '', null, request)
|
||||
expect(result.error).toBeDefined()
|
||||
})
|
||||
|
||||
it('should login', async () => {
|
||||
await methods.createAccount(metricsContext, db, '', {
|
||||
await methods.createAccount(metricsContext, db, '', null, {
|
||||
method: 'createAccount',
|
||||
params: ['andrey', '123']
|
||||
})
|
||||
await methods.createWorkspace(metricsContext, db, '', {
|
||||
await methods.createWorkspace(metricsContext, db, '', null, {
|
||||
method: 'createWorkspace',
|
||||
params: [workspace, 'ООО Рога и Копыта']
|
||||
})
|
||||
await methods.assignWorkspace(metricsContext, db, '', {
|
||||
await methods.assignWorkspace(metricsContext, db, '', null, {
|
||||
method: 'assignWorkspace',
|
||||
params: ['andrey', workspace]
|
||||
})
|
||||
@ -100,7 +100,7 @@ describe('server', () => {
|
||||
params: ['andrey', '123', workspace]
|
||||
}
|
||||
|
||||
const result = await methods.login(metricsContext, db, '', request)
|
||||
const result = await methods.login(metricsContext, db, '', null, request)
|
||||
expect(result.result).toBeDefined()
|
||||
})
|
||||
|
||||
@ -110,7 +110,7 @@ describe('server', () => {
|
||||
params: ['andrey', '123555', workspace]
|
||||
}
|
||||
|
||||
const result = await methods.login(metricsContext, db, '', request)
|
||||
const result = await methods.login(metricsContext, db, '', null, request)
|
||||
expect(result.error).toBeDefined()
|
||||
})
|
||||
|
||||
@ -120,7 +120,7 @@ describe('server', () => {
|
||||
params: ['andrey1', '123555', workspace]
|
||||
}
|
||||
|
||||
const result = await methods.login(metricsContext, db, '', request)
|
||||
const result = await methods.login(metricsContext, db, '', null, request)
|
||||
expect(result.error).toBeDefined()
|
||||
})
|
||||
|
||||
@ -130,20 +130,20 @@ describe('server', () => {
|
||||
params: ['andrey', '123', 'non-existent-workspace']
|
||||
}
|
||||
|
||||
const result = await methods.login(metricsContext, db, '', request)
|
||||
const result = await methods.login(metricsContext, db, '', null, request)
|
||||
expect(result.error).toBeDefined()
|
||||
})
|
||||
|
||||
it('do remove workspace', async () => {
|
||||
await methods.createAccount(metricsContext, db, '', {
|
||||
await methods.createAccount(metricsContext, db, '', null, {
|
||||
method: 'createAccount',
|
||||
params: ['andrey', '123']
|
||||
})
|
||||
await methods.createWorkspace(metricsContext, db, '', {
|
||||
await methods.createWorkspace(metricsContext, db, '', null, {
|
||||
method: 'createWorkspace',
|
||||
params: [workspace, 'ООО Рога и Копыта']
|
||||
})
|
||||
await methods.assignWorkspace(metricsContext, db, '', {
|
||||
await methods.assignWorkspace(metricsContext, db, '', null, {
|
||||
method: 'assignWorkspace',
|
||||
params: ['andrey', workspace]
|
||||
})
|
||||
@ -152,7 +152,7 @@ describe('server', () => {
|
||||
expect((await getAccount(db, 'andrey'))?.workspaces.length).toEqual(1)
|
||||
expect((await getWorkspaceByUrl(db, '', workspace))?.accounts.length).toEqual(1)
|
||||
|
||||
await methods.removeWorkspace(metricsContext, db, '', {
|
||||
await methods.removeWorkspace(metricsContext, db, '', null, {
|
||||
method: 'removeWorkspace',
|
||||
params: ['andrey', workspace]
|
||||
})
|
||||
|
@ -234,6 +234,7 @@ async function getAccountInfo (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
email: string,
|
||||
password: string
|
||||
): Promise<AccountInfo> {
|
||||
@ -254,6 +255,7 @@ async function getAccountInfoByToken (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
token: string
|
||||
): Promise<LoginInfo> {
|
||||
let email: string = ''
|
||||
@ -290,12 +292,13 @@ export async function login (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
_email: string,
|
||||
password: string
|
||||
): Promise<LoginInfo> {
|
||||
const email = cleanEmail(_email)
|
||||
try {
|
||||
const info = await getAccountInfo(ctx, db, productId, email, password)
|
||||
const info = await getAccountInfo(ctx, db, productId, branding, email, password)
|
||||
const result = {
|
||||
endpoint: getEndpoint(),
|
||||
email,
|
||||
@ -330,6 +333,7 @@ export async function selectWorkspace (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
token: string,
|
||||
workspaceUrl: string,
|
||||
allowAdmin: boolean = true
|
||||
@ -429,6 +433,7 @@ export async function join (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
_email: string,
|
||||
password: string,
|
||||
inviteId: ObjectId
|
||||
@ -441,14 +446,15 @@ export async function join (
|
||||
ctx,
|
||||
db,
|
||||
productId,
|
||||
branding,
|
||||
email,
|
||||
workspace.name,
|
||||
invite?.role ?? AccountRole.User,
|
||||
invite?.personId
|
||||
)
|
||||
|
||||
const token = (await login(ctx, db, productId, email, password)).token
|
||||
const result = await selectWorkspace(ctx, db, productId, token, ws.workspaceUrl ?? ws.workspace)
|
||||
const token = (await login(ctx, db, productId, branding, email, password)).token
|
||||
const result = await selectWorkspace(ctx, db, productId, branding, token, ws.workspaceUrl ?? ws.workspace)
|
||||
await useInvite(db, inviteId)
|
||||
return result
|
||||
}
|
||||
@ -476,7 +482,13 @@ export async function confirmEmail (db: Db, _email: string): Promise<Account> {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function confirm (ctx: MeasureContext, db: Db, productId: string, token: string): Promise<LoginInfo> {
|
||||
export async function confirm (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
token: string
|
||||
): Promise<LoginInfo> {
|
||||
const decode = decodeToken(token)
|
||||
const _email = decode.extra?.confirm
|
||||
if (_email === undefined) {
|
||||
@ -495,7 +507,7 @@ export async function confirm (ctx: MeasureContext, db: Db, productId: string, t
|
||||
return result
|
||||
}
|
||||
|
||||
async function sendConfirmation (productId: string, account: Account): Promise<void> {
|
||||
async function sendConfirmation (productId: string, branding: Branding | null, account: Account): Promise<void> {
|
||||
const sesURL = getMetadata(accountPlugin.metadata.SES_URL)
|
||||
if (sesURL === undefined || sesURL === '') {
|
||||
console.info('Please provide email service url to enable email confirmations.')
|
||||
@ -516,10 +528,11 @@ async function sendConfirmation (productId: string, account: Account): Promise<v
|
||||
|
||||
const link = concatLink(front, `/login/confirm?id=${token}`)
|
||||
|
||||
const name = getMetadata(accountPlugin.metadata.ProductName)
|
||||
const text = await translate(accountPlugin.string.ConfirmationText, { name, link })
|
||||
const html = await translate(accountPlugin.string.ConfirmationHTML, { name, link })
|
||||
const subject = await translate(accountPlugin.string.ConfirmationSubject, { name })
|
||||
const name = branding?.title ?? getMetadata(accountPlugin.metadata.ProductName)
|
||||
const lang = branding?.language
|
||||
const text = await translate(accountPlugin.string.ConfirmationText, { name, link }, lang)
|
||||
const html = await translate(accountPlugin.string.ConfirmationHTML, { name, link }, lang)
|
||||
const subject = await translate(accountPlugin.string.ConfirmationSubject, { name }, lang)
|
||||
|
||||
if (sesURL !== undefined && sesURL !== '') {
|
||||
const to = account.email
|
||||
@ -545,6 +558,7 @@ export async function signUpJoin (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
_email: string,
|
||||
password: string,
|
||||
first: string,
|
||||
@ -560,6 +574,7 @@ export async function signUpJoin (
|
||||
ctx,
|
||||
db,
|
||||
productId,
|
||||
branding,
|
||||
email,
|
||||
password,
|
||||
first,
|
||||
@ -570,14 +585,15 @@ export async function signUpJoin (
|
||||
ctx,
|
||||
db,
|
||||
productId,
|
||||
branding,
|
||||
email,
|
||||
workspace.name,
|
||||
invite?.role ?? AccountRole.User,
|
||||
invite?.personId
|
||||
)
|
||||
|
||||
const token = (await login(ctx, db, productId, email, password)).token
|
||||
const result = await selectWorkspace(ctx, db, productId, token, ws.workspaceUrl ?? ws.workspace)
|
||||
const token = (await login(ctx, db, productId, branding, email, password)).token
|
||||
const result = await selectWorkspace(ctx, db, productId, branding, token, ws.workspaceUrl ?? ws.workspace)
|
||||
await useInvite(db, inviteId)
|
||||
return result
|
||||
}
|
||||
@ -589,6 +605,7 @@ export async function createAcc (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
_email: string,
|
||||
password: string | null,
|
||||
first: string,
|
||||
@ -631,7 +648,7 @@ export async function createAcc (
|
||||
const sesURL = getMetadata(accountPlugin.metadata.SES_URL)
|
||||
if (!confirmed) {
|
||||
if (sesURL !== undefined && sesURL !== '') {
|
||||
await sendConfirmation(productId, newAccount)
|
||||
await sendConfirmation(productId, branding, newAccount)
|
||||
} else {
|
||||
ctx.info('Please provide email service url to enable email confirmations.')
|
||||
await confirmEmail(db, email)
|
||||
@ -648,6 +665,7 @@ export async function createAccount (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
_email: string,
|
||||
password: string,
|
||||
first: string,
|
||||
@ -659,6 +677,7 @@ export async function createAccount (
|
||||
ctx,
|
||||
db,
|
||||
productId,
|
||||
branding,
|
||||
email,
|
||||
password,
|
||||
first,
|
||||
@ -681,6 +700,7 @@ export async function listWorkspaces (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
token: string
|
||||
): Promise<WorkspaceInfo[]> {
|
||||
decodeToken(token) // Just verify token is valid
|
||||
@ -739,7 +759,7 @@ export async function cleanInProgressWorkspaces (db: Db, productId: string): Pro
|
||||
).map((it) => ({ ...it, productId }))
|
||||
const ctx = new MeasureMetricsContext('clean', {})
|
||||
for (const d of toDelete) {
|
||||
await dropWorkspace(ctx, db, productId, d.workspace)
|
||||
await dropWorkspace(ctx, db, productId, null, d.workspace)
|
||||
}
|
||||
}
|
||||
|
||||
@ -878,6 +898,7 @@ export async function createWorkspace (
|
||||
migrationOperation: [string, MigrateOperation][],
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
email: string,
|
||||
workspaceName: string,
|
||||
workspace?: string,
|
||||
@ -915,7 +936,7 @@ export async function createWorkspace (
|
||||
}
|
||||
let model: Tx[] = []
|
||||
try {
|
||||
const initWS = getMetadata(toolPlugin.metadata.InitWorkspace)
|
||||
const initWS = branding?.initWorkspace ?? getMetadata(toolPlugin.metadata.InitWorkspace)
|
||||
const wsId = getWorkspaceId(workspaceInfo.workspace, productId)
|
||||
|
||||
// We should not try to clone INIT_WS into INIT_WS during it's creation.
|
||||
@ -1034,7 +1055,14 @@ export async function upgradeWorkspace (
|
||||
*/
|
||||
export const createUserWorkspace =
|
||||
(version: Data<Version>, txes: Tx[], migrationOperation: [string, MigrateOperation][]) =>
|
||||
async (ctx: MeasureContext, db: Db, productId: string, token: string, workspaceName: string): Promise<LoginInfo> => {
|
||||
async (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
token: string,
|
||||
workspaceName: string
|
||||
): Promise<LoginInfo> => {
|
||||
const { email } = decodeToken(token)
|
||||
|
||||
ctx.info('Creating workspace', { workspaceName, email })
|
||||
@ -1064,12 +1092,13 @@ export const createUserWorkspace =
|
||||
migrationOperation,
|
||||
db,
|
||||
productId,
|
||||
branding,
|
||||
email,
|
||||
workspaceName,
|
||||
undefined,
|
||||
notifyHandler,
|
||||
async (workspace, model) => {
|
||||
const initWS = getMetadata(toolPlugin.metadata.InitWorkspace)
|
||||
const initWS = branding?.initWorkspace ?? getMetadata(toolPlugin.metadata.InitWorkspace)
|
||||
const shouldUpdateAccount = initWS !== undefined && (await getWorkspaceById(db, productId, initWS)) !== null
|
||||
const client = await connect(
|
||||
getTransactor(),
|
||||
@ -1085,6 +1114,7 @@ export const createUserWorkspace =
|
||||
ctx,
|
||||
db,
|
||||
productId,
|
||||
branding,
|
||||
email,
|
||||
workspace.workspace,
|
||||
AccountRole.Owner,
|
||||
@ -1145,6 +1175,7 @@ export async function getInviteLink (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
token: string,
|
||||
exp: number,
|
||||
emailMask: string,
|
||||
@ -1202,6 +1233,7 @@ export async function getUserWorkspaces (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
token: string
|
||||
): Promise<ClientWorkspaceInfo[]> {
|
||||
const { email } = decodeToken(token)
|
||||
@ -1227,6 +1259,7 @@ export async function getWorkspaceInfo (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
token: string,
|
||||
_updateLastVisit: boolean = false
|
||||
): Promise<ClientWorkspaceInfo> {
|
||||
@ -1337,10 +1370,11 @@ export async function createMissingEmployee (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
token: string
|
||||
): Promise<void> {
|
||||
const { email } = decodeToken(token)
|
||||
const wsInfo = await getWorkspaceInfo(ctx, db, productId, token)
|
||||
const wsInfo = await getWorkspaceInfo(ctx, db, productId, branding, token)
|
||||
const account = await getAccount(db, email)
|
||||
|
||||
if (account === null) {
|
||||
@ -1357,6 +1391,7 @@ export async function assignWorkspace (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
_email: string,
|
||||
workspaceId: string,
|
||||
role: AccountRole,
|
||||
@ -1366,7 +1401,7 @@ export async function assignWorkspace (
|
||||
personAccountId?: Ref<PersonAccount>
|
||||
): Promise<Workspace> {
|
||||
const email = cleanEmail(_email)
|
||||
const initWS = getMetadata(toolPlugin.metadata.InitWorkspace)
|
||||
const initWS = branding?.initWorkspace ?? getMetadata(toolPlugin.metadata.InitWorkspace)
|
||||
if (initWS !== undefined && initWS === workspaceId) {
|
||||
Analytics.handleError(new Error(`assign-workspace failed ${email} ${workspaceId}`))
|
||||
ctx.error('assign-workspace failed', { email, workspaceId, reason: 'initWs === workspaceId' })
|
||||
@ -1571,12 +1606,13 @@ export async function changePassword (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
token: string,
|
||||
oldPassword: string,
|
||||
password: string
|
||||
): Promise<void> {
|
||||
const { email } = decodeToken(token)
|
||||
const account = await getAccountInfo(ctx, db, productId, email, oldPassword)
|
||||
const account = await getAccountInfo(ctx, db, productId, branding, email, oldPassword)
|
||||
|
||||
const salt = randomBytes(32)
|
||||
const hash = hashWithSalt(password, salt)
|
||||
@ -1611,7 +1647,13 @@ export async function replacePassword (db: Db, productId: string, email: string,
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function requestPassword (ctx: MeasureContext, db: Db, productId: string, _email: string): Promise<void> {
|
||||
export async function requestPassword (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
_email: string
|
||||
): Promise<void> {
|
||||
const email = cleanEmail(_email)
|
||||
const account = await getAccount(db, email)
|
||||
|
||||
@ -1638,10 +1680,10 @@ export async function requestPassword (ctx: MeasureContext, db: Db, productId: s
|
||||
)
|
||||
|
||||
const link = concatLink(front, `/login/recovery?id=${token}`)
|
||||
|
||||
const text = await translate(accountPlugin.string.RecoveryText, { link })
|
||||
const html = await translate(accountPlugin.string.RecoveryHTML, { link })
|
||||
const subject = await translate(accountPlugin.string.RecoverySubject, {})
|
||||
const lang = branding?.language
|
||||
const text = await translate(accountPlugin.string.RecoveryText, { link }, lang)
|
||||
const html = await translate(accountPlugin.string.RecoveryHTML, { link }, lang)
|
||||
const subject = await translate(accountPlugin.string.RecoverySubject, {}, lang)
|
||||
|
||||
const to = account.email
|
||||
await fetch(concatLink(sesURL, '/send'), {
|
||||
@ -1666,6 +1708,7 @@ export async function restorePassword (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
token: string,
|
||||
password: string
|
||||
): Promise<LoginInfo> {
|
||||
@ -1682,7 +1725,7 @@ export async function restorePassword (
|
||||
|
||||
await updatePassword(db, account, password)
|
||||
|
||||
return await login(ctx, db, productId, email, password)
|
||||
return await login(ctx, db, productId, branding, email, password)
|
||||
}
|
||||
|
||||
async function updatePassword (db: Db, account: Account, password: string | null): Promise<void> {
|
||||
@ -1699,6 +1742,7 @@ export async function removeWorkspace (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
email: string,
|
||||
workspaceId: string
|
||||
): Promise<void> {
|
||||
@ -1719,6 +1763,7 @@ export async function checkJoin (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
token: string,
|
||||
inviteId: ObjectId
|
||||
): Promise<WorkspaceLoginInfo> {
|
||||
@ -1732,7 +1777,7 @@ export async function checkJoin (
|
||||
new Status(Severity.ERROR, platform.status.WorkspaceNotFound, { workspace: workspace.name })
|
||||
)
|
||||
}
|
||||
return await selectWorkspace(ctx, db, productId, token, ws?.workspaceUrl ?? ws.workspace, false)
|
||||
return await selectWorkspace(ctx, db, productId, branding, token, ws?.workspaceUrl ?? ws.workspace, false)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1742,6 +1787,7 @@ export async function dropWorkspace (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
workspaceId: string
|
||||
): Promise<Workspace> {
|
||||
const ws = await getWorkspaceById(db, productId, workspaceId)
|
||||
@ -1765,10 +1811,11 @@ export async function dropWorkspaceFull (
|
||||
db: Db,
|
||||
client: MongoClient,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
workspaceId: string,
|
||||
storageAdapter?: StorageAdapter
|
||||
): Promise<void> {
|
||||
const ws = await dropWorkspace(ctx, db, productId, workspaceId)
|
||||
const ws = await dropWorkspace(ctx, db, productId, branding, workspaceId)
|
||||
const workspaceDb = client.db(ws.workspace)
|
||||
await workspaceDb.dropDatabase()
|
||||
const wspace = getWorkspaceId(workspaceId, productId)
|
||||
@ -1782,7 +1829,13 @@ export async function dropWorkspaceFull (
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function dropAccount (ctx: MeasureContext, db: Db, productId: string, email: string): Promise<void> {
|
||||
export async function dropAccount (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
email: string
|
||||
): Promise<void> {
|
||||
const account = await getAccount(db, email)
|
||||
if (account === null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotFound, { account: email }))
|
||||
@ -1813,6 +1866,7 @@ export async function leaveWorkspace (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
token: string,
|
||||
email: string
|
||||
): Promise<void> {
|
||||
@ -1851,6 +1905,7 @@ export async function sendInvite (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
token: string,
|
||||
email: string,
|
||||
personId?: Ref<Person>,
|
||||
@ -1885,13 +1940,14 @@ export async function sendInvite (
|
||||
const expHours = 48
|
||||
const exp = expHours * 60 * 60 * 1000
|
||||
|
||||
const inviteId = await getInviteLink(ctx, db, productId, token, exp, email, 1)
|
||||
const inviteId = await getInviteLink(ctx, db, productId, branding, token, exp, email, 1)
|
||||
const link = concatLink(front, `/login/join?inviteId=${inviteId.toString()}`)
|
||||
|
||||
const ws = workspace.workspaceName ?? workspace.workspace
|
||||
const text = await translate(accountPlugin.string.InviteText, { link, ws, expHours })
|
||||
const html = await translate(accountPlugin.string.InviteHTML, { link, ws, expHours })
|
||||
const subject = await translate(accountPlugin.string.InviteSubject, { ws })
|
||||
const lang = branding?.language
|
||||
const text = await translate(accountPlugin.string.InviteText, { link, ws, expHours }, lang)
|
||||
const html = await translate(accountPlugin.string.InviteHTML, { link, ws, expHours }, lang)
|
||||
const subject = await translate(accountPlugin.string.InviteSubject, { ws }, lang)
|
||||
|
||||
const to = email
|
||||
await fetch(concatLink(sesURL, '/send'), {
|
||||
@ -1935,6 +1991,14 @@ async function deactivatePersonAccount (
|
||||
}
|
||||
}
|
||||
|
||||
export interface Branding {
|
||||
title?: string
|
||||
language?: string
|
||||
initWorkspace?: string
|
||||
}
|
||||
|
||||
export type BrandingMap = Record<string, Branding>
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -1942,16 +2006,30 @@ export type AccountMethod = (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
request: any,
|
||||
token?: string
|
||||
) => Promise<any>
|
||||
|
||||
function wrap (
|
||||
accountMethod: (ctx: MeasureContext, db: Db, productId: string, ...args: any[]) => Promise<any>
|
||||
accountMethod: (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
...args: any[]
|
||||
) => Promise<any>
|
||||
): AccountMethod {
|
||||
return async function (ctx: MeasureContext, db: Db, productId: string, request: any, token?: string): Promise<any> {
|
||||
return async function (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
request: any,
|
||||
token?: string
|
||||
): Promise<any> {
|
||||
if (token !== undefined) request.params.unshift(token)
|
||||
return await accountMethod(ctx, db, productId, ...request.params)
|
||||
return await accountMethod(ctx, db, productId, branding, ...request.params)
|
||||
.then((result) => ({ id: request.id, result }))
|
||||
.catch((err) => {
|
||||
const status =
|
||||
@ -1975,6 +2053,7 @@ export async function joinWithProvider (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
_email: string,
|
||||
first: string,
|
||||
last: string,
|
||||
@ -2013,29 +2092,39 @@ export async function joinWithProvider (
|
||||
ctx,
|
||||
db,
|
||||
productId,
|
||||
branding,
|
||||
email,
|
||||
workspace.name,
|
||||
invite?.role ?? AccountRole.User,
|
||||
invite?.personId
|
||||
)
|
||||
const result = await selectWorkspace(ctx, db, productId, token, wsRes.workspaceUrl ?? wsRes.workspace, false)
|
||||
const result = await selectWorkspace(
|
||||
ctx,
|
||||
db,
|
||||
productId,
|
||||
branding,
|
||||
token,
|
||||
wsRes.workspaceUrl ?? wsRes.workspace,
|
||||
false
|
||||
)
|
||||
|
||||
await useInvite(db, inviteId)
|
||||
return result
|
||||
}
|
||||
|
||||
const newAccount = await createAcc(ctx, db, productId, email, null, first, last, true, extra)
|
||||
const newAccount = await createAcc(ctx, db, productId, branding, email, null, first, last, true, extra)
|
||||
const token = generateToken(email, getWorkspaceId('', productId), getExtra(newAccount))
|
||||
const ws = await assignWorkspace(
|
||||
ctx,
|
||||
db,
|
||||
productId,
|
||||
branding,
|
||||
email,
|
||||
workspace.name,
|
||||
invite?.role ?? AccountRole.User,
|
||||
invite?.personId
|
||||
)
|
||||
const result = await selectWorkspace(ctx, db, productId, token, ws.workspaceUrl ?? ws.workspace, false)
|
||||
const result = await selectWorkspace(ctx, db, productId, branding, token, ws.workspaceUrl ?? ws.workspace, false)
|
||||
|
||||
await useInvite(db, inviteId)
|
||||
|
||||
@ -2046,6 +2135,7 @@ export async function loginWithProvider (
|
||||
ctx: MeasureContext,
|
||||
db: Db,
|
||||
productId: string,
|
||||
branding: Branding | null,
|
||||
_email: string,
|
||||
first: string,
|
||||
last: string,
|
||||
@ -2071,7 +2161,7 @@ export async function loginWithProvider (
|
||||
}
|
||||
return result
|
||||
}
|
||||
const newAccount = await createAcc(ctx, db, productId, email, null, first, last, true, extra)
|
||||
const newAccount = await createAcc(ctx, db, productId, branding, email, null, first, last, true, extra)
|
||||
|
||||
const result = {
|
||||
endpoint: getEndpoint(),
|
||||
|
@ -244,10 +244,7 @@ export function start (
|
||||
calendarUrl: string
|
||||
collaboratorUrl: string
|
||||
collaboratorApiUrl: string
|
||||
title?: string
|
||||
languages: string
|
||||
defaultLanguage: string
|
||||
lastNameFirst?: string
|
||||
brandingUrl?: string
|
||||
},
|
||||
port: number,
|
||||
extraConfig?: Record<string, string | undefined>
|
||||
@ -281,10 +278,7 @@ export function start (
|
||||
CALENDAR_URL: config.calendarUrl,
|
||||
COLLABORATOR_URL: config.collaboratorUrl,
|
||||
COLLABORATOR_API_URL: config.collaboratorApiUrl,
|
||||
TITLE: config.title,
|
||||
LANGUAGES: config.languages,
|
||||
DEFAULT_LANGUAGE: config.defaultLanguage,
|
||||
LAST_NAME_FIRST: config.lastNameFirst,
|
||||
BRANDING_URL: config.brandingUrl,
|
||||
...(extraConfig ?? {})
|
||||
}
|
||||
res.set('Cache-Control', cacheControlNoCache)
|
||||
|
@ -22,8 +22,6 @@ import serverToken from '@hcengineering/server-token'
|
||||
import { start } from '.'
|
||||
|
||||
export function startFront (ctx: MeasureContext, extraConfig?: Record<string, string | undefined>): void {
|
||||
const defaultLanguage = process.env.DEFAULT_LANGUAGE ?? 'en'
|
||||
const languages = process.env.LANGUAGES ?? 'en,ru'
|
||||
const SERVER_PORT = parseInt(process.env.SERVER_PORT ?? '8080')
|
||||
|
||||
const transactorEndpoint = process.env.TRANSACTOR_URL
|
||||
@ -107,9 +105,7 @@ export function startFront (ctx: MeasureContext, extraConfig?: Record<string, st
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const lastNameFirst = process.env.LAST_NAME_FIRST
|
||||
|
||||
const title = process.env.TITLE
|
||||
const brandingUrl = process.env.BRANDING_URL
|
||||
|
||||
setMetadata(serverToken.metadata.Secret, serverSecret)
|
||||
|
||||
@ -121,15 +117,12 @@ export function startFront (ctx: MeasureContext, extraConfig?: Record<string, st
|
||||
uploadUrl,
|
||||
modelVersion,
|
||||
gmailUrl,
|
||||
lastNameFirst,
|
||||
telegramUrl,
|
||||
rekoniUrl,
|
||||
calendarUrl,
|
||||
collaboratorUrl,
|
||||
collaboratorApiUrl,
|
||||
title,
|
||||
languages,
|
||||
defaultLanguage
|
||||
brandingUrl
|
||||
}
|
||||
console.log('Starting Front service with', config)
|
||||
const shutdown = start(ctx, config, SERVER_PORT, extraConfig)
|
||||
|
31
tests/branding-test.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"localhost:8083": {
|
||||
"title": "Platform",
|
||||
"languages": "en,ru,pt,es",
|
||||
"defaultLanguage": "en",
|
||||
"defaultApplication": "tracker",
|
||||
"defaultSpace": "tracker:project:DefaultProject",
|
||||
"defaultSpecial": "issues",
|
||||
"lastNameFirst": "true",
|
||||
"links": [
|
||||
{
|
||||
"rel": "manifest",
|
||||
"href": "/platform/site.webmanifest"
|
||||
},
|
||||
{
|
||||
"rel": "icon",
|
||||
"href": "/platform/favicon.svg",
|
||||
"type": "image/svg+xml"
|
||||
},
|
||||
{
|
||||
"rel": "shortcut icon",
|
||||
"href": "/platform/favicon.ico",
|
||||
"sizes": "any"
|
||||
},
|
||||
{
|
||||
"rel": "apple-touch-icon",
|
||||
"href": "/platform/icon-192.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -59,6 +59,8 @@ services:
|
||||
- transactor
|
||||
ports:
|
||||
- 8083:8083
|
||||
volumes:
|
||||
- ./branding-test.json:/app/dist/branding-test.json
|
||||
environment:
|
||||
- SERVER_PORT=8083
|
||||
- SERVER_SECRET=secret
|
||||
@ -74,7 +76,7 @@ services:
|
||||
- COLLABORATOR_URL=ws://localhost:3079
|
||||
- COLLABORATOR_API_URL=http://localhost:3079
|
||||
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
||||
- LAST_NAME_FIRST=true
|
||||
- BRANDING_URL=/branding-test.json
|
||||
transactor:
|
||||
image: hardcoreeng/transactor
|
||||
pull_policy: never
|
||||
|
@ -13,7 +13,7 @@ export class LeftSideMenuPage extends CommonPage {
|
||||
buttonContacts = (): Locator => this.page.locator('button[id$="Contacts"]')
|
||||
buttonTracker = (): Locator => this.page.locator('button[id$="TrackerApplication"]')
|
||||
buttonNotification = (): Locator => this.page.locator('button[id$="Inbox"]')
|
||||
buttonDocuments = (): Locator => this.page.locator('button[id$="DocumentApplication"]')
|
||||
buttonDocuments = (): Locator => this.page.locator('button[id$="document:string:DocumentApplication"]')
|
||||
profileButton = (): Locator => this.page.locator('#profile-button')
|
||||
inviteToWorkspaceButton = (): Locator => this.page.locator('button:has-text("Invite to workspace")')
|
||||
getInviteLinkButton = (): Locator => this.page.locator('button:has-text("Get invite link")')
|
||||
|