mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-31 04:38:02 +00:00
Sign up (#781)
Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
parent
a973d2a0fd
commit
459d7b3261
@ -10653,12 +10653,13 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/account.tgz:
|
||||
resolution: {integrity: sha512-HlxJJR7cADZNKLQaAHv0ecnw44cK/zfmS3fcQZrt91+BJnytc9MwLOjpetYtS6ktCXKOTdKp2DtYZubFDDl2zw==, tarball: file:projects/account.tgz}
|
||||
resolution: {integrity: sha512-g8Jj5vatEBYxczIayNpyhAJypuGS17w8t4htZB0ljRlctQ9IQ69fMKfuVYDb8OTKMZuhuadDthRAqqsJevuxvA==, tarball: file:projects/account.tgz}
|
||||
name: '@rush-temp/account'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
'@rushstack/heft': 0.41.8
|
||||
'@types/heft-jest': 1.0.2
|
||||
'@types/minio': 7.0.11
|
||||
'@typescript-eslint/eslint-plugin': 5.7.0_c25e8c1f4f4f7aaed27aa6f9ce042237
|
||||
'@typescript-eslint/parser': 5.7.0_eslint@7.32.0+typescript@4.5.4
|
||||
eslint: 7.32.0
|
||||
@ -10667,6 +10668,7 @@ packages:
|
||||
eslint-plugin-node: 11.1.0_eslint@7.32.0
|
||||
eslint-plugin-promise: 5.2.0_eslint@7.32.0
|
||||
jwt-simple: 0.5.6
|
||||
minio: 7.0.25
|
||||
mongodb: 4.2.2
|
||||
prettier: 2.5.1
|
||||
typescript: 4.5.4
|
||||
@ -11045,7 +11047,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/dev-client-resources.tgz:
|
||||
resolution: {integrity: sha512-hcwOdV5TgwA6yTNeZDtMwAaGEOYFO5VzqWTXk+sK+SPSiEN2yy6rb2Do1cfgL0wvSXLH/F2VT+nr1WAlI909lQ==, tarball: file:projects/dev-client-resources.tgz}
|
||||
resolution: {integrity: sha512-1SUKqC2CJ2uWW0XbQeYN8DVtc2/6w0abs3IXzQ763VWWdkTVecBg6MjoD42LgH4HyrSL/u1J4AV8/o98Tbuslw==, tarball: file:projects/dev-client-resources.tgz}
|
||||
name: '@rush-temp/dev-client-resources'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -11364,7 +11366,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/lead-resources.tgz_096c09b0b673a57c275d9767a12070b1:
|
||||
resolution: {integrity: sha512-Bko+x/XDsWFrhZ4EfXCZ8GwS56DMgWA5t2tYGyPF7nlzIPY97oJWPZGc2z1T7olyx0bye+Od9Vk7qmZI3KuBkQ==, tarball: file:projects/lead-resources.tgz}
|
||||
resolution: {integrity: sha512-W842udh6/87OvajsjespWfSRZhSUJZPkGDSHCaMjtSePN5T4nL2EkNGR66mIi1RW88oAoyd3VPx8MSZmUWnCGQ==, tarball: file:projects/lead-resources.tgz}
|
||||
id: file:projects/lead-resources.tgz
|
||||
name: '@rush-temp/lead-resources'
|
||||
version: 0.0.0
|
||||
@ -11425,7 +11427,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/login-resources.tgz_096c09b0b673a57c275d9767a12070b1:
|
||||
resolution: {integrity: sha512-MnzXjBUZFvM2+oyagvYkpisissgM0dTtcCgFApyoRq2lzlpXdrbybP9ujMBYliqJ7i4r6rk4Wk8HrRo/wfYfyg==, tarball: file:projects/login-resources.tgz}
|
||||
resolution: {integrity: sha512-85HwyicQhgSqbvQ/Lb+/qMxRZmCxC71u7HaZMVcw92Fa3Mu3wEH0kB0JHyPjvMA/mgrs6uvJmPh5bJwQHhkB0Q==, tarball: file:projects/login-resources.tgz}
|
||||
id: file:projects/login-resources.tgz
|
||||
name: '@rush-temp/login-resources'
|
||||
version: 0.0.0
|
||||
@ -11568,7 +11570,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/model-contact.tgz_typescript@4.5.4:
|
||||
resolution: {integrity: sha512-ffJA0hXhTeEjKGbCFKTgYk9ch8JsAnZKmDOtyTSzE+tmIklA0Q1gWQGwqttPSgrGVQuy2UXkYXv1m7cnhBICxg==, tarball: file:projects/model-contact.tgz}
|
||||
resolution: {integrity: sha512-GD5NmMEaz2KD79NdbNlQTbmuc98hxeV0cwV8xgRtCZnbeNrDw4h71yUik4vGNLsgha8ezy2oefrdi5aET1wbig==, tarball: file:projects/model-contact.tgz}
|
||||
id: file:projects/model-contact.tgz
|
||||
name: '@rush-temp/model-contact'
|
||||
version: 0.0.0
|
||||
@ -12004,7 +12006,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/pod-account.tgz:
|
||||
resolution: {integrity: sha512-LqJ4mBkjSWhBA9OiiMUgLeoUF9IniLh1xr/G3iXRHSm8Li6OssMSTZ733CAoWVNPchAMn5xBibLV5Mi6syer9w==, tarball: file:projects/pod-account.tgz}
|
||||
resolution: {integrity: sha512-OULVsjJpuo9JVOV9Ru1HO3GYCxRIOHlYqJvdtEezTG6HcorbC6i3+cQat8fbdGV2LQ8Lpup55Q5k40t1OrEAIA==, tarball: file:projects/pod-account.tgz}
|
||||
name: '@rush-temp/pod-account'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -12018,6 +12020,7 @@ packages:
|
||||
'@types/node': 16.11.14
|
||||
'@typescript-eslint/eslint-plugin': 5.7.0_c25e8c1f4f4f7aaed27aa6f9ce042237
|
||||
'@typescript-eslint/parser': 5.7.0_eslint@7.32.0+typescript@4.5.4
|
||||
cross-env: 7.0.3
|
||||
esbuild: 0.12.29
|
||||
eslint: 7.32.0
|
||||
eslint-config-standard-with-typescript: 21.0.1_ce2fa0c4dfa1c256100cababd749a13a
|
||||
|
@ -47,6 +47,9 @@ services:
|
||||
environment:
|
||||
- MONGO_URL=mongodb://mongodb:27017
|
||||
- TRANSACTOR_URL=ws://localhost:3333
|
||||
- MINIO_ENDPOINT=minio
|
||||
- MINIO_ACCESS_KEY=minioadmin
|
||||
- MINIO_SECRET_KEY=minioadmin
|
||||
front:
|
||||
image: anticrm/front
|
||||
links:
|
||||
|
@ -25,16 +25,13 @@ import {
|
||||
listWorkspaces,
|
||||
listAccounts
|
||||
} from '@anticrm/account'
|
||||
import contact, { combineName } from '@anticrm/contact'
|
||||
import core, { TxOperations } from '@anticrm/core'
|
||||
import { program } from 'commander'
|
||||
import { Client } from 'minio'
|
||||
import { Db, MongoClient } from 'mongodb'
|
||||
import { connect } from './connect'
|
||||
import { rebuildElastic } from './elastic'
|
||||
import { importXml } from './importer'
|
||||
import { clearTelegramHistory } from './telegram'
|
||||
import { diffWorkspace, dumpWorkspace, initWorkspace, restoreWorkspace, upgradeWorkspace } from './workspace'
|
||||
import { diffWorkspace, dumpWorkspace, restoreWorkspace, upgradeWorkspace } from './workspace'
|
||||
|
||||
const mongodbUri = process.env.MONGO_URL
|
||||
if (mongodbUri === undefined) {
|
||||
@ -109,35 +106,8 @@ program
|
||||
.description('assign workspace')
|
||||
.action(async (email: string, workspace: string, cmd) => {
|
||||
return await withDatabase(mongodbUri, async (db, client) => {
|
||||
console.log(`retrieveing account from ${email}...`)
|
||||
const account = await getAccount(db, email)
|
||||
if (account === null) {
|
||||
throw new Error('account not found')
|
||||
}
|
||||
|
||||
console.log(`assigning user ${email} to ${workspace}...`)
|
||||
await assignWorkspace(db, email, workspace)
|
||||
|
||||
console.log('connecting to transactor...')
|
||||
const connection = await connect(transactorUrl, workspace)
|
||||
const ops = new TxOperations(connection, core.account.System)
|
||||
|
||||
const name = combineName(account.first, account.last)
|
||||
|
||||
console.log('create user in target workspace...')
|
||||
const employee = await ops.createDoc(contact.class.Employee, contact.space.Employee, {
|
||||
name,
|
||||
city: 'Mountain View',
|
||||
channels: []
|
||||
})
|
||||
|
||||
console.log('create account in target workspace...')
|
||||
await ops.createDoc(contact.class.EmployeeAccount, core.space.Model, {
|
||||
email,
|
||||
employee,
|
||||
name
|
||||
})
|
||||
await connection.close()
|
||||
})
|
||||
})
|
||||
|
||||
@ -158,7 +128,6 @@ program
|
||||
.action(async (workspace, cmd) => {
|
||||
return await withDatabase(mongodbUri, async (db) => {
|
||||
await createWorkspace(db, workspace, cmd.organization)
|
||||
await initWorkspace(mongodbUri, workspace, transactorUrl, minio)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
import contact from '@anticrm/contact'
|
||||
import core, { DOMAIN_TX, Tx } from '@anticrm/core'
|
||||
import builder, { migrateOperations, createDeps } from '@anticrm/model-all'
|
||||
import builder, { migrateOperations } from '@anticrm/model-all'
|
||||
import { existsSync } from 'fs'
|
||||
import { mkdir, open, readFile, writeFile } from 'fs/promises'
|
||||
import { Client } from 'minio'
|
||||
@ -30,46 +30,6 @@ import { rebuildElastic } from './elastic'
|
||||
|
||||
const txes = JSON.parse(JSON.stringify(builder.getTxes())) as Tx[]
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function initWorkspace (
|
||||
mongoUrl: string,
|
||||
dbName: string,
|
||||
transactorUrl: string,
|
||||
minio: Client
|
||||
): Promise<void> {
|
||||
if (txes.some(tx => tx.objectSpace !== core.space.Model)) {
|
||||
throw Error('Model txes must target only core.space.Model')
|
||||
}
|
||||
|
||||
const client = new MongoClient(mongoUrl)
|
||||
try {
|
||||
await client.connect()
|
||||
const db = client.db(dbName)
|
||||
|
||||
console.log('dropping database...')
|
||||
await db.dropDatabase()
|
||||
|
||||
console.log('creating model...')
|
||||
const model = txes
|
||||
const result = await db.collection(DOMAIN_TX).insertMany(model as Document[])
|
||||
console.log(`${result.insertedCount} model transactions inserted.`)
|
||||
|
||||
console.log('creating data...')
|
||||
const connection = await connect(transactorUrl, dbName)
|
||||
await createDeps(connection)
|
||||
await connection.close()
|
||||
|
||||
console.log('create minio bucket')
|
||||
if (!(await minio.bucketExists(dbName))) {
|
||||
await minio.makeBucket(dbName, 'k8s')
|
||||
}
|
||||
} finally {
|
||||
await client.close()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -79,7 +39,7 @@ export async function upgradeWorkspace (
|
||||
transactorUrl: string,
|
||||
minio: Client
|
||||
): Promise<void> {
|
||||
if (txes.some(tx => tx.objectSpace !== core.space.Model)) {
|
||||
if (txes.some((tx) => tx.objectSpace !== core.space.Model)) {
|
||||
throw Error('Model txes must target only core.space.Model')
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@
|
||||
export let error: string | undefined = undefined
|
||||
export let password: boolean | undefined = undefined
|
||||
export let id: string | undefined = undefined
|
||||
export let name: string | undefined = undefined
|
||||
</script>
|
||||
|
||||
<div class="editbox{error ? ' error' : ''}" style={width ? 'width: ' + width : ''}>
|
||||
@ -30,6 +31,7 @@
|
||||
type="password"
|
||||
class:nolabel={!label}
|
||||
{id}
|
||||
{name}
|
||||
bind:value
|
||||
on:blur
|
||||
on:change
|
||||
@ -42,6 +44,7 @@
|
||||
type="text"
|
||||
class:nolabel={!label}
|
||||
{id}
|
||||
{name}
|
||||
bind:value
|
||||
on:blur
|
||||
on:change
|
||||
|
@ -23,7 +23,7 @@ import Root from './components/internal/Root.svelte'
|
||||
|
||||
export type { AnyComponent, AnySvelteComponent, Action, LabelAndProps, TooltipAligment } from './types'
|
||||
// export { applicationShortcutKey } from './utils'
|
||||
export { getCurrentLocation, navigate, location } from './location'
|
||||
export { getCurrentLocation, locationToUrl, navigate, location } from './location'
|
||||
|
||||
export { default as EditBox } from './components/EditBox.svelte'
|
||||
export { default as Label } from './components/Label.svelte'
|
||||
@ -106,16 +106,23 @@ interface CompAndProps {
|
||||
|
||||
export const popupstore = writable<CompAndProps[]>([])
|
||||
|
||||
export function showPopup (component: AnySvelteComponent | AnyComponent, props: any, element?: PopupAlignment, onClose?: (result: any) => void): void {
|
||||
export function showPopup (
|
||||
component: AnySvelteComponent | AnyComponent,
|
||||
props: any,
|
||||
element?: PopupAlignment,
|
||||
onClose?: (result: any) => void
|
||||
): void {
|
||||
if (typeof component === 'string') {
|
||||
getResource(component).then(resolved => {
|
||||
popupstore.update(popups => {
|
||||
popups.push({ is: resolved, props, element, onClose })
|
||||
return popups
|
||||
getResource(component)
|
||||
.then((resolved) => {
|
||||
popupstore.update((popups) => {
|
||||
popups.push({ is: resolved, props, element, onClose })
|
||||
return popups
|
||||
})
|
||||
})
|
||||
}).catch(err => console.log(err))
|
||||
.catch((err) => console.log(err))
|
||||
} else {
|
||||
popupstore.update(popups => {
|
||||
popupstore.update((popups) => {
|
||||
popups.push({ is: component, props, element, onClose })
|
||||
return popups
|
||||
})
|
||||
@ -123,7 +130,7 @@ export function showPopup (component: AnySvelteComponent | AnyComponent, props:
|
||||
}
|
||||
|
||||
export function closePopup (): void {
|
||||
popupstore.update(popups => {
|
||||
popupstore.update((popups) => {
|
||||
popups.pop()
|
||||
return popups
|
||||
})
|
||||
@ -138,16 +145,37 @@ export const tooltipstore = writable<LabelAndProps>({
|
||||
anchor: undefined
|
||||
})
|
||||
|
||||
export function showTooltip (label: IntlString | undefined, element: HTMLElement, direction?: TooltipAligment, component?: AnySvelteComponent | AnyComponent, props?: any, anchor?: HTMLElement): void {
|
||||
tooltipstore.set({ label: label, element: element, direction: direction, component: component, props: props, anchor: anchor })
|
||||
export function showTooltip (
|
||||
label: IntlString | undefined,
|
||||
element: HTMLElement,
|
||||
direction?: TooltipAligment,
|
||||
component?: AnySvelteComponent | AnyComponent,
|
||||
props?: any,
|
||||
anchor?: HTMLElement
|
||||
): void {
|
||||
tooltipstore.set({
|
||||
label: label,
|
||||
element: element,
|
||||
direction: direction,
|
||||
component: component,
|
||||
props: props,
|
||||
anchor: anchor
|
||||
})
|
||||
}
|
||||
|
||||
export function closeTooltip (): void {
|
||||
tooltipstore.set({ label: undefined, element: undefined, direction: undefined, component: undefined, props: undefined, anchor: undefined })
|
||||
tooltipstore.set({
|
||||
label: undefined,
|
||||
element: undefined,
|
||||
direction: undefined,
|
||||
component: undefined,
|
||||
props: undefined,
|
||||
anchor: undefined
|
||||
})
|
||||
}
|
||||
|
||||
export const ticker = readable(Date.now(), set => {
|
||||
const interval = setInterval(() => {
|
||||
export const ticker = readable(Date.now(), (set) => {
|
||||
setInterval(() => {
|
||||
set(Date.now())
|
||||
}, 10000)
|
||||
})
|
||||
|
@ -1,7 +1,27 @@
|
||||
{
|
||||
"status": {
|
||||
"RequiredField": "Required field {field}",
|
||||
"FieldsDoNotMatch": "{field} don't match {field2}",
|
||||
"ConnectingToServer": "Connecting to server....",
|
||||
"IncorrectValue": "Incorrect value {field}"
|
||||
},
|
||||
"string": {
|
||||
"LogIn": "Login",
|
||||
"SignUp": "Sign Up",
|
||||
"DoNotHaveAnAccount": "Do not have an account?"
|
||||
"CreateWorkspace": "Create workspace",
|
||||
"HaveWorkspace": "Already have a workspace?",
|
||||
"LastName": "Last name",
|
||||
"FirstName": "First name",
|
||||
"Join": "Join",
|
||||
"Email": "Email",
|
||||
"Password": "Password",
|
||||
"Workspace": "Workspace",
|
||||
"DoNotHaveAnAccount": "Do not have an account?",
|
||||
"PasswordRepeat": "Repeat password",
|
||||
"HaveAccount": "Already have an account?",
|
||||
"SelectWorkspace": "Select workspace",
|
||||
"Copy": "Copy",
|
||||
"Close": "Close",
|
||||
"InviteDescription": "Share this link to invite other users"
|
||||
}
|
||||
}
|
@ -33,6 +33,7 @@
|
||||
"@anticrm/platform": "~0.6.5",
|
||||
"svelte": "^3.37.0",
|
||||
"@anticrm/login": "~0.6.1",
|
||||
"@anticrm/account": "~0.6.0",
|
||||
"@anticrm/ui": "~0.6.0",
|
||||
"@anticrm/workbench": "~0.6.1"
|
||||
}
|
||||
|
@ -0,0 +1,68 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021, 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Status, Severity } from '@anticrm/platform'
|
||||
|
||||
import Form from './Form.svelte'
|
||||
import { createWorkspace } from '../utils'
|
||||
import { getCurrentLocation, navigate, setMetadataLocalStorage, showPopup } from '@anticrm/ui'
|
||||
import login from '../plugin'
|
||||
import workbench from '@anticrm/workbench'
|
||||
import InviteLink from './InviteLink.svelte'
|
||||
|
||||
const fields = [{ name: 'workspace', i18n: login.string.Workspace, rule: /^\S+$/ }]
|
||||
|
||||
const object = {
|
||||
workspace: ''
|
||||
}
|
||||
|
||||
let status = new Status(Severity.OK, 0, '')
|
||||
|
||||
const action = {
|
||||
i18n: login.string.CreateWorkspace,
|
||||
func: async () => {
|
||||
status = new Status(Severity.INFO, login.status.ConnectingToServer, {})
|
||||
|
||||
const [loginStatus, result] = await createWorkspace(object.workspace)
|
||||
status = loginStatus
|
||||
|
||||
if (result !== undefined) {
|
||||
setMetadataLocalStorage(login.metadata.LoginToken, result.token)
|
||||
setMetadataLocalStorage(login.metadata.LoginEndpoint, result.endpoint)
|
||||
setMetadataLocalStorage(login.metadata.LoginEmail, result.email)
|
||||
showPopup(InviteLink, {}, undefined, () => {
|
||||
navigate({ path: [workbench.component.WorkbenchApp] })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form
|
||||
caption={login.string.CreateWorkspace}
|
||||
{status}
|
||||
{fields}
|
||||
{object}
|
||||
{action}
|
||||
bottomCaption={login.string.HaveWorkspace}
|
||||
bottomActionLabel={login.string.SelectWorkspace}
|
||||
bottomActionFunc={() => {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[1] = 'selectWorkspace'
|
||||
loc.path.length = 2
|
||||
navigate(loc)
|
||||
}}
|
||||
/>
|
@ -1,5 +1,6 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021, 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
@ -22,11 +23,13 @@
|
||||
import login from '../plugin'
|
||||
|
||||
interface Field {
|
||||
id?: string
|
||||
name: string
|
||||
i18n: IntlString
|
||||
password?: boolean
|
||||
optional?: boolean
|
||||
short?: boolean
|
||||
rule?: RegExp
|
||||
}
|
||||
|
||||
interface Action {
|
||||
@ -51,6 +54,25 @@
|
||||
status = new Status(Severity.INFO, login.status.RequiredField, { field: await translate(field.i18n, {}) })
|
||||
return
|
||||
}
|
||||
if (f.id !== undefined) {
|
||||
const sameFields = fields.filter((f) => f.id === field.id)
|
||||
for (const field of sameFields) {
|
||||
const v = object[field.name]
|
||||
if (v !== object[f.name]) {
|
||||
status = new Status(Severity.INFO, login.status.FieldsDoNotMatch, {
|
||||
field: await translate(field.i18n, {}),
|
||||
field2: await translate(f.i18n, {})
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if (f.rule !== undefined) {
|
||||
if (!f.rule.test(v)) {
|
||||
status = new Status(Severity.INFO, login.status.IncorrectValue, { field: await translate(field.i18n, {}) })
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
status = OK
|
||||
}
|
||||
@ -84,6 +106,7 @@
|
||||
<div class={field.short ? 'form-col' : 'form-row'}>
|
||||
<StylishEdit
|
||||
label={field.i18n}
|
||||
name={field.id}
|
||||
password={field.password}
|
||||
bind:value={object[field.name]}
|
||||
on:keyup={validate}
|
||||
@ -107,12 +130,6 @@
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- <div class="form-col"><EditBox label="First Name" bind:value={fname}/></div>
|
||||
<div class="form-col"><EditBox label="Last Name" bind:value={lname}/></div>
|
||||
<div class="form-row"><EditBox label="E-mail"/></div>
|
||||
<div class="form-row"><EditBox label="Password" password/></div>
|
||||
<div class="form-row"><EditBox label="Repeat password" password/></div> -->
|
||||
</div>
|
||||
<div class="grow-separator" />
|
||||
<div class="footer">
|
||||
@ -121,11 +138,6 @@
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- <div class="actions">
|
||||
{#each actions as action, i}
|
||||
<button class="button" class:separator={i !== 0} on:click|preventDefault={action.func}> {action.i18n} </button>
|
||||
{/each}
|
||||
</div> -->
|
||||
<style lang="scss">
|
||||
.container {
|
||||
display: flex;
|
||||
|
89
plugins/login-resources/src/components/InviteLink.svelte
Normal file
89
plugins/login-resources/src/components/InviteLink.svelte
Normal file
@ -0,0 +1,89 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021, 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the 'License');
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an 'AS IS' BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Button, getCurrentLocation, Label, locationToUrl } from '@anticrm/ui'
|
||||
import { getWorkspaceHash } from '../utils'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import login from '../plugin'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function getLink (): Promise<string> {
|
||||
const hash = await getWorkspaceHash()
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[0] = login.component.LoginApp
|
||||
loc.path[1] = 'join'
|
||||
loc.path.length = 2
|
||||
loc.query = {
|
||||
workspace: hash
|
||||
}
|
||||
const link = locationToUrl(loc)
|
||||
return document.location.origin + link
|
||||
}
|
||||
|
||||
function copy (link: string): void {
|
||||
navigator.clipboard.writeText(link)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="popup">
|
||||
<div class="fs-title flex-center">
|
||||
<Label label={login.string.InviteDescription} />
|
||||
</div>
|
||||
{#await getLink() then link}
|
||||
<div class="link">{link}</div>
|
||||
<div class="buttons flex">
|
||||
<Button
|
||||
label={login.string.Copy}
|
||||
size="small"
|
||||
on:click={() => {
|
||||
copy(link)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
label={login.string.Close}
|
||||
size="small"
|
||||
primary
|
||||
on:click={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/await}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.popup {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem;
|
||||
color: var(--theme-caption-color);
|
||||
background-color: var(--theme-button-bg-hovered);
|
||||
border: 1px solid var(--theme-button-border-enabled);
|
||||
border-radius: 0.75rem;
|
||||
filter: drop-shadow(0 1.5rem 4rem rgba(0, 0, 0, 0.35));
|
||||
|
||||
.link {
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
100
plugins/login-resources/src/components/Join.svelte
Normal file
100
plugins/login-resources/src/components/Join.svelte
Normal file
@ -0,0 +1,100 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021, 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { OK, Status, Severity } from '@anticrm/platform'
|
||||
import { getCurrentLocation, navigate, setMetadataLocalStorage } from '@anticrm/ui'
|
||||
|
||||
import Form from './Form.svelte'
|
||||
import { join, signUpJoin } from '../utils'
|
||||
|
||||
import login from '../plugin'
|
||||
import workbench from '@anticrm/workbench'
|
||||
|
||||
const location = getCurrentLocation()
|
||||
|
||||
let page = 'login'
|
||||
|
||||
$: fields =
|
||||
page === 'login'
|
||||
? [
|
||||
{ id: 'email', name: 'username', i18n: login.string.Email },
|
||||
{
|
||||
id: 'current-password',
|
||||
name: 'password',
|
||||
i18n: login.string.Password,
|
||||
password: true
|
||||
}
|
||||
]
|
||||
: [
|
||||
{ id: 'given-name', name: 'first', i18n: login.string.FirstName, short: true },
|
||||
{ id: 'family-name', name: 'last', i18n: login.string.LastName, short: true },
|
||||
{ id: 'email', name: 'username', i18n: login.string.Email },
|
||||
{ id: 'new-password', name: 'password', i18n: login.string.Password, password: true },
|
||||
{ id: 'new-password', name: 'password2', i18n: login.string.PasswordRepeat, password: true }
|
||||
]
|
||||
|
||||
$: object = {
|
||||
first: '',
|
||||
last: '',
|
||||
username: '',
|
||||
password: '',
|
||||
password2: ''
|
||||
}
|
||||
|
||||
let status = OK
|
||||
|
||||
$: action = {
|
||||
i18n: login.string.Join,
|
||||
func: async () => {
|
||||
status = new Status(Severity.INFO, login.status.ConnectingToServer, {})
|
||||
|
||||
const [loginStatus, result] =
|
||||
page === 'login'
|
||||
? await join(object.username, object.password, location.query?.workspace ?? '')
|
||||
: await signUpJoin(
|
||||
object.username,
|
||||
object.password,
|
||||
object.first,
|
||||
object.last,
|
||||
location.query?.workspace ?? ''
|
||||
)
|
||||
status = loginStatus
|
||||
|
||||
if (result !== undefined) {
|
||||
setMetadataLocalStorage(login.metadata.LoginToken, result.token)
|
||||
setMetadataLocalStorage(login.metadata.LoginEndpoint, result.endpoint)
|
||||
setMetadataLocalStorage(login.metadata.LoginEmail, result.email)
|
||||
navigate({ path: [workbench.component.WorkbenchApp] })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$: bottomCaption = page === 'login' ? login.string.DoNotHaveAnAccount : login.string.DoNotHaveAnAccount
|
||||
$: bottomActionLabel = page === 'login' ? login.string.SignUp : login.string.LogIn
|
||||
</script>
|
||||
|
||||
<Form
|
||||
caption={login.string.Join}
|
||||
{status}
|
||||
{fields}
|
||||
{object}
|
||||
{action}
|
||||
{bottomCaption}
|
||||
{bottomActionLabel}
|
||||
bottomActionFunc={() => {
|
||||
page = page === 'login' ? 'signUp' : 'login'
|
||||
}}
|
||||
/>
|
@ -1,42 +1,64 @@
|
||||
<!--
|
||||
// Copyright © 2020 Anticrm Platform Contributors.
|
||||
//
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021, 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { fetchMetadataLocalStorage, location, Popup } from '@anticrm/ui'
|
||||
|
||||
import LoginForm from './LoginForm.svelte'
|
||||
import SignupForm from './SignupForm.svelte'
|
||||
import CreateWorkspaceForm from './CreateWorkspaceForm.svelte'
|
||||
import SelectWorkspace from './SelectWorkspace.svelte'
|
||||
import Join from './Join.svelte'
|
||||
import { onDestroy } from 'svelte'
|
||||
import login from '../plugin'
|
||||
|
||||
let page = 'login'
|
||||
export let page: string = 'login'
|
||||
|
||||
const token = fetchMetadataLocalStorage(login.metadata.LoginToken)
|
||||
|
||||
onDestroy(
|
||||
location.subscribe(async (loc) => {
|
||||
page = loc.path[1] ?? (token ? 'selectWorkspace' : 'login')
|
||||
})
|
||||
)
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<div class="panel">
|
||||
{#if page === 'login'}
|
||||
<LoginForm on:switch={(event) => page = event.detail}/>
|
||||
<LoginForm />
|
||||
{:else if page === 'signup'}
|
||||
<SignupForm on:switch={(event) => page = event.detail}/>
|
||||
<SignupForm />
|
||||
{:else if page === 'createWorkspace'}
|
||||
<CreateWorkspaceForm />
|
||||
{:else if page === 'selectWorkspace'}
|
||||
<SelectWorkspace />
|
||||
{:else if page === 'join'}
|
||||
<Join />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="intro">
|
||||
<div class="content">
|
||||
<div class="logo"> </div>
|
||||
<div class="logo" />
|
||||
</div>
|
||||
<div class="slogan">
|
||||
<p>A unique place to manage all of your work</p>
|
||||
<p>Welcome to the Platform</p>
|
||||
</div>
|
||||
</div>
|
||||
<Popup />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@ -71,21 +93,21 @@
|
||||
position: relative;
|
||||
&:after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
background: center url("../../img/logo.svg");
|
||||
content: '';
|
||||
background: center url('../../img/logo.svg');
|
||||
transform: translate(-50%, -50%);
|
||||
width: 63px;
|
||||
height: 79px;
|
||||
}
|
||||
&:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
content: '';
|
||||
transform: translate(-50%, -50%);
|
||||
width: 16rem;
|
||||
height: 16rem;
|
||||
border: 1.8px solid var(--theme-caption-color);
|
||||
border-radius: 50%;
|
||||
opacity: .08;
|
||||
opacity: 0.08;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -94,12 +116,12 @@
|
||||
p {
|
||||
margin: 0;
|
||||
font-weight: 400;
|
||||
font-size: .8rem;
|
||||
font-size: 0.8rem;
|
||||
text-align: center;
|
||||
color: var(--theme-caption-color);
|
||||
opacity: .8;
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
@ -1,72 +1,77 @@
|
||||
<!--
|
||||
// Copyright © 2020 Anticrm Platform Contributors.
|
||||
//
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021, 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { OK, Status, Severity } from '@anticrm/platform'
|
||||
import { navigate, setMetadataLocalStorage } from '@anticrm/ui'
|
||||
import { getCurrentLocation, navigate, setMetadataLocalStorage } from '@anticrm/ui'
|
||||
|
||||
import Form from './Form.svelte'
|
||||
import { doLogin } from '../utils'
|
||||
|
||||
import login from '../plugin'
|
||||
import workbench from '@anticrm/workbench'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const fields = [
|
||||
{ name: 'username', i18n: login.string.Email },
|
||||
{ id: 'email', name: 'username', i18n: login.string.Email },
|
||||
{
|
||||
id: 'current-password',
|
||||
name: 'password',
|
||||
i18n: login.string.Password,
|
||||
password: true
|
||||
},
|
||||
{ name: 'workspace', i18n: login.string.Workspace }
|
||||
}
|
||||
]
|
||||
|
||||
const object = {
|
||||
workspace: '',
|
||||
username: '',
|
||||
password: '',
|
||||
password: ''
|
||||
}
|
||||
|
||||
let status = OK
|
||||
|
||||
const action = {
|
||||
const action = {
|
||||
i18n: login.string.LogIn,
|
||||
func: async () => {
|
||||
func: async () => {
|
||||
status = new Status(Severity.INFO, login.status.ConnectingToServer, {})
|
||||
|
||||
const [loginStatus, result] = await doLogin(object.username, object.password, object.workspace)
|
||||
const [loginStatus, result] = await doLogin(object.username, object.password)
|
||||
status = loginStatus
|
||||
|
||||
if (result !== undefined) {
|
||||
console.log('token', result.token)
|
||||
setMetadataLocalStorage(login.metadata.LoginToken, result.token)
|
||||
setMetadataLocalStorage(login.metadata.LoginEndpoint, result.endpoint)
|
||||
setMetadataLocalStorage(login.metadata.LoginEmail, result.email)
|
||||
navigate({ path: [workbench.component.WorkbenchApp] })
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[1] = 'selectWorkspace'
|
||||
loc.path.length = 2
|
||||
navigate(loc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<Form caption={login.string.LogIn} {status} {fields} {object} {action}
|
||||
<Form
|
||||
caption={login.string.LogIn}
|
||||
{status}
|
||||
{fields}
|
||||
{object}
|
||||
{action}
|
||||
bottomCaption={login.string.DoNotHaveAnAccount}
|
||||
bottomActionLabel={login.string.SignUp}
|
||||
bottomActionFunc={() => { dispatch('switch', 'signup') }}
|
||||
bottomActionFunc={() => {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[1] = 'signup'
|
||||
loc.path.length = 2
|
||||
navigate(loc)
|
||||
}}
|
||||
/>
|
||||
|
@ -1,146 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
//
|
||||
// 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.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Platform } from '@anticrm/plugin'
|
||||
import { AuthStatusCodes } from '@anticrm/plugin'
|
||||
import { Severity, Status } from '@anticrm/status'
|
||||
import { getContext, onDestroy } from 'svelte'
|
||||
import type { LoginService } from '@anticrm/plugin-login'
|
||||
import login from '@anticrm/plugin-login'
|
||||
import Form from './Form.svelte'
|
||||
import { Button } from '@anticrm/ui'
|
||||
import type { ApplicationRouter } from '@anticrm/plugin-ui'
|
||||
// import { PlatformStatusCodes } from '@anticrm/foundation'
|
||||
|
||||
// export let router: ApplicationRouter<ApplicationRoute>
|
||||
const object = { username: '', password: '', workspace: '', secondFactorCode: '' }
|
||||
let status: Status
|
||||
|
||||
let loginActive = false
|
||||
let needSecondFactor = false
|
||||
const baseFields = [
|
||||
{ name: 'username', i18n: 'Username' },
|
||||
{
|
||||
name: 'password',
|
||||
i18n: 'Password',
|
||||
password: true
|
||||
},
|
||||
{ name: 'workspace', i18n: 'Workspace' }
|
||||
]
|
||||
|
||||
let fields: { [key: string]: any }
|
||||
$: fields = needSecondFactor ? baseFields.concat({ name: 'secondFactorCode', i18n: 'Confirm code' }) : baseFields
|
||||
|
||||
const platform = getContext('platform') as Platform
|
||||
const loginService = platform.getPlugin(login.id)
|
||||
|
||||
async function doLogin () {
|
||||
status = new Status(Severity.INFO, 0, 'Соединяюсь с сервером...')
|
||||
|
||||
status = await (await loginService).doLogin(
|
||||
object.username,
|
||||
object.password,
|
||||
object.workspace,
|
||||
object.secondFactorCode
|
||||
)
|
||||
|
||||
if (status.code === AuthStatusCodes.CLIENT_VALIDATE_REQUIRED) {
|
||||
needSecondFactor = true
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
async function doSignup () {}
|
||||
|
||||
async function checkLoginInfo (ls: LoginService) {
|
||||
const info = await ls.getLoginInfo()
|
||||
if (info) {
|
||||
loginActive = true
|
||||
object.username = info.email
|
||||
object.workspace = info.workspace
|
||||
}
|
||||
}
|
||||
|
||||
let timer: number
|
||||
// Auto forward to default application
|
||||
const loginCheck = loginService.then(async (ls) => {
|
||||
await checkLoginInfo(ls)
|
||||
|
||||
timer = setInterval(async () => {
|
||||
await checkLoginInfo(ls)
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
if (timer) {
|
||||
clearInterval(timer)
|
||||
}
|
||||
})
|
||||
|
||||
async function navigateApp (): Promise<void> {
|
||||
(await loginService).navigateApp()
|
||||
}
|
||||
|
||||
async function logout (): Promise<void> {
|
||||
(await loginService).doLogout()
|
||||
loginActive = false
|
||||
}
|
||||
|
||||
function navigateSetting (): void {
|
||||
// router.navigate({ route: 'setting' })
|
||||
}
|
||||
</script>
|
||||
|
||||
{#await loginCheck then value}
|
||||
{#if loginActive}
|
||||
<div class="login-form-info">
|
||||
<div class="field">
|
||||
Logged in as: {object.username}
|
||||
</div>
|
||||
<div class="field">
|
||||
Workspace: {object.workspace}
|
||||
</div>
|
||||
<div class="actions">
|
||||
<Button width="100px" on:click={logout}>Logout</Button>
|
||||
<Button width="100px" on:click={navigateSetting}>Settings</Button>
|
||||
<Button width="100px" on:click={navigateApp}>Switch to Application</Button>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<Form
|
||||
actions={[
|
||||
{ i18n: 'Create Space', func: doSignup },
|
||||
{ i18n: 'Login', func: doLogin }
|
||||
]}
|
||||
{fields}
|
||||
{object}
|
||||
caption="Login into system"
|
||||
{status} />
|
||||
{/if}
|
||||
{/await}
|
||||
|
||||
<style lang="scss">
|
||||
.login-form-info {
|
||||
margin: 20vh auto auto;
|
||||
width: 30em;
|
||||
padding: 2em;
|
||||
border-radius: 1em;
|
||||
border: 1px solid var(--theme-bg-accent-color);
|
||||
.actions {
|
||||
display: flex;
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,45 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
//
|
||||
// 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.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import login from '..'
|
||||
import type { AnyComponent, ApplicationRoute } from '@anticrm/platform-ui'
|
||||
import { newRouter } from '@anticrm/platform-ui'
|
||||
import Component from '@anticrm/platform-ui/src/components/Component.svelte'
|
||||
|
||||
let form: ApplicationRoute
|
||||
const forms: ApplicationRoute[] = [{ route: 'setting', component: login.component.SettingForm }]
|
||||
|
||||
let component: AnyComponent | undefined
|
||||
|
||||
function routeDefaults (): ApplicationRoute {
|
||||
return {
|
||||
route: '#undefined',
|
||||
component: login.component.LoginForm
|
||||
} as ApplicationRoute
|
||||
}
|
||||
|
||||
const router = newRouter<ApplicationRoute>(
|
||||
':route',
|
||||
(info) => {
|
||||
if (forms.length > 0) {
|
||||
form = forms.find((a) => a.route === info.route) || routeDefaults()
|
||||
component = form?.component
|
||||
}
|
||||
},
|
||||
routeDefaults()
|
||||
)
|
||||
</script>
|
||||
|
||||
<Component is={component} props={{ router }} />
|
138
plugins/login-resources/src/components/SelectWorkspace.svelte
Normal file
138
plugins/login-resources/src/components/SelectWorkspace.svelte
Normal file
@ -0,0 +1,138 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021, 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Status, Severity, OK } from '@anticrm/platform'
|
||||
|
||||
import { getWorkspaces, selectWorkspace } from '../utils'
|
||||
import { Button, getCurrentLocation, Label, navigate, setMetadataLocalStorage } from '@anticrm/ui'
|
||||
|
||||
import workbench from '@anticrm/workbench'
|
||||
import login from '../plugin'
|
||||
import StatusControl from './StatusControl.svelte'
|
||||
|
||||
let status = OK
|
||||
|
||||
async function select (workspace: string) {
|
||||
status = new Status(Severity.INFO, login.status.ConnectingToServer, {})
|
||||
|
||||
const [loginStatus, result] = await selectWorkspace(workspace)
|
||||
status = loginStatus
|
||||
|
||||
if (result !== undefined) {
|
||||
setMetadataLocalStorage(login.metadata.LoginToken, result.token)
|
||||
setMetadataLocalStorage(login.metadata.LoginEndpoint, result.endpoint)
|
||||
setMetadataLocalStorage(login.metadata.LoginEmail, result.email)
|
||||
navigate({ path: [workbench.component.WorkbenchApp] })
|
||||
}
|
||||
}
|
||||
|
||||
function createWorkspace (): void {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[1] = 'createWorkspace'
|
||||
loc.path.length = 2
|
||||
navigate(loc)
|
||||
}
|
||||
</script>
|
||||
|
||||
<form class="container">
|
||||
<div class="grow-separator" />
|
||||
<div class="title"><Label label={login.string.SelectWorkspace} /></div>
|
||||
<div class="status">
|
||||
<StatusControl {status} />
|
||||
</div>
|
||||
{#await getWorkspaces() then workspaces}
|
||||
<div class="form">
|
||||
{#each workspaces as workspace (workspace._id)}
|
||||
<div
|
||||
class="workspace flex-center fs-title cursor-pointer focused-button form-row"
|
||||
on:click={() => select(workspace.workspace)}
|
||||
>
|
||||
{workspace.workspace}
|
||||
</div>
|
||||
{/each}
|
||||
{#if !workspaces.length}
|
||||
<div class="form-row send">
|
||||
<Button label={login.string.CreateWorkspace} primary width="100%" on:click={createWorkspace} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="grow-separator" />
|
||||
{#if workspaces.length}
|
||||
<div class="footer">
|
||||
<span><Label label={login.string.CreateWorkspace} /></span>
|
||||
<a href="." on:click|preventDefault={createWorkspace}><Label label={login.string.CreateWorkspace} /></a>
|
||||
</div>
|
||||
{/if}
|
||||
{/await}
|
||||
</form>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
padding: 5rem;
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
font-size: 1.5rem;
|
||||
color: var(--theme-caption-color);
|
||||
}
|
||||
.status {
|
||||
min-height: 7.5rem;
|
||||
max-height: 7.5rem;
|
||||
padding-top: 1.25rem;
|
||||
}
|
||||
|
||||
.form {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
column-gap: 0.75rem;
|
||||
row-gap: 1.5rem;
|
||||
|
||||
.form-row {
|
||||
grid-column-start: 1;
|
||||
grid-column-end: 3;
|
||||
}
|
||||
|
||||
.workspace {
|
||||
padding: 1rem;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
}
|
||||
.grow-separator {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.footer {
|
||||
margin-top: 3.5rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--theme-caption-color);
|
||||
span {
|
||||
opacity: 0.3;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--theme-caption-color);
|
||||
opacity: 0.8;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,68 +1,78 @@
|
||||
<!--
|
||||
// Copyright © 2020 Anticrm Platform Contributors.
|
||||
//
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021, 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { getContext, createEventDispatcher } from 'svelte'
|
||||
import { Status, Severity } from '@anticrm/platform'
|
||||
|
||||
import Form from './Form.svelte'
|
||||
import { doLogin } from '../utils'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
import { signUp } from '../utils'
|
||||
import login from '../plugin'
|
||||
import { getCurrentLocation, navigate, setMetadataLocalStorage } from '@anticrm/ui'
|
||||
|
||||
const fields = [
|
||||
{ name: 'first', i18n: 'First name', short: true },
|
||||
{ name: 'last', i18n: 'Last name', short: true },
|
||||
{ name: 'username', i18n: 'Email' },
|
||||
{ name: 'workspace', i18n: 'Workspace' },
|
||||
{ name: 'password', i18n: 'Password', password: true },
|
||||
{ name: 'password2', i18n: 'Repeat password', password: true },
|
||||
{ id: 'given-name', name: 'first', i18n: login.string.FirstName, short: true },
|
||||
{ id: 'family-name', name: 'last', i18n: login.string.LastName, short: true },
|
||||
{ id: 'email', name: 'username', i18n: login.string.Email },
|
||||
{ id: 'new-password', name: 'password', i18n: login.string.Password, password: true },
|
||||
{ id: 'new-password', name: 'password2', i18n: login.string.PasswordRepeat, password: true }
|
||||
]
|
||||
|
||||
const object = {
|
||||
first: '',
|
||||
last: '',
|
||||
workspace: '',
|
||||
username: '',
|
||||
password: '',
|
||||
password2: '',
|
||||
password2: ''
|
||||
}
|
||||
|
||||
let status = new Status(Severity.OK, 0, '')
|
||||
|
||||
const action = {
|
||||
i18n: 'Sign Up',
|
||||
func: async () => {
|
||||
status = new Status(Severity.INFO, 0, 'Соединяюсь с сервером...')
|
||||
const action = {
|
||||
i18n: login.string.SignUp,
|
||||
func: async () => {
|
||||
status = new Status(Severity.INFO, login.status.ConnectingToServer, {})
|
||||
|
||||
const [loginStatus, result] = await doLogin(object.username, object.password, object.workspace)
|
||||
const [loginStatus, result] = await signUp(object.username, object.password, object.first, object.last)
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
status = loginStatus
|
||||
resolve()
|
||||
}, 1000)
|
||||
})
|
||||
status = loginStatus
|
||||
|
||||
if (result !== undefined) {
|
||||
setMetadataLocalStorage(login.metadata.LoginToken, result.token)
|
||||
setMetadataLocalStorage(login.metadata.LoginEndpoint, result.endpoint)
|
||||
setMetadataLocalStorage(login.metadata.LoginEmail, result.email)
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[1] = 'selectWorkspace'
|
||||
loc.path.length = 2
|
||||
navigate(loc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<Form caption="Sign Up" {status} {fields} {object} {action}
|
||||
bottomCaption="Already have an account?"
|
||||
bottomActionLabel="Log In"
|
||||
bottomActionFunc={() => { dispatch('switch', 'login') }}
|
||||
<Form
|
||||
caption={login.string.SignUp}
|
||||
{status}
|
||||
{fields}
|
||||
{object}
|
||||
{action}
|
||||
bottomCaption={login.string.HaveAccount}
|
||||
bottomActionLabel={login.string.LogIn}
|
||||
bottomActionFunc={() => {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[1] = 'login'
|
||||
loc.path.length = 2
|
||||
navigate(loc)
|
||||
}}
|
||||
/>
|
||||
|
@ -12,7 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import type { Status } from '@anticrm/platform'
|
||||
import { Severity } from '@anticrm/platform'
|
||||
@ -23,17 +22,17 @@
|
||||
</script>
|
||||
|
||||
{#if status.severity !== Severity.OK}
|
||||
<div class="flex-row-center container" class:error={status.severity === Severity.ERROR}>
|
||||
<StatusControl {status} />
|
||||
</div>
|
||||
<div class="flex-row-center container" class:error={status.severity === Severity.ERROR}>
|
||||
<StatusControl {status} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
padding: .75rem 1rem;
|
||||
padding: 0.75rem 1rem;
|
||||
background-color: var(--theme-bg-accent-color);
|
||||
border: 1px solid var(--theme-bg-accent-hover);
|
||||
border-radius: .5rem;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.error {
|
||||
@ -42,4 +41,4 @@
|
||||
background-color: var(--theme-button-bg-error);
|
||||
border-color: var(--system-error-60-color);
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
@ -26,5 +26,5 @@ import LoginApp from './components/LoginApp.svelte'
|
||||
export default async () => ({
|
||||
component: {
|
||||
LoginApp
|
||||
},
|
||||
}
|
||||
})
|
||||
|
@ -16,21 +16,33 @@
|
||||
|
||||
import type { StatusCode, IntlString } from '@anticrm/platform'
|
||||
import { mergeIds } from '@anticrm/platform'
|
||||
import type { AnyComponent } from '@anticrm/ui'
|
||||
|
||||
import login, { loginId } from '@anticrm/login'
|
||||
|
||||
export default mergeIds(loginId, login, {
|
||||
status: {
|
||||
RequiredField: '' as StatusCode<{ field: string }>,
|
||||
ConnectingToServer: '' as StatusCode
|
||||
FieldsDoNotMatch: '' as StatusCode<{ field: string, field2: string }>,
|
||||
ConnectingToServer: '' as StatusCode,
|
||||
IncorrectValue: '' as StatusCode<{ field: string }>
|
||||
},
|
||||
string: {
|
||||
CreateWorkspace: '' as IntlString,
|
||||
HaveWorkspace: '' as IntlString,
|
||||
LastName: '' as IntlString,
|
||||
FirstName: '' as IntlString,
|
||||
HaveAccount: '' as IntlString,
|
||||
Join: '' as IntlString,
|
||||
Email: '' as IntlString,
|
||||
Password: '' as IntlString,
|
||||
PasswordRepeat: '' as IntlString,
|
||||
Workspace: '' as IntlString,
|
||||
LogIn: '' as IntlString,
|
||||
SignUp: '' as IntlString,
|
||||
DoNotHaveAnAccount: '' as IntlString
|
||||
SelectWorkspace: '' as IntlString,
|
||||
DoNotHaveAnAccount: '' as IntlString,
|
||||
Copy: '' as IntlString,
|
||||
Close: '' as IntlString,
|
||||
InviteDescription: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
@ -13,12 +13,14 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Status, OK, unknownError, getMetadata, serialize } from '@anticrm/platform'
|
||||
import { Status, OK, unknownError, getMetadata, serialize, unknownStatus } from '@anticrm/platform'
|
||||
import type { Request, Response } from '@anticrm/platform'
|
||||
import type { Workspace } from '@anticrm/account'
|
||||
|
||||
import login from '@anticrm/login'
|
||||
import { fetchMetadataLocalStorage, getCurrentLocation, navigate } from '@anticrm/ui'
|
||||
|
||||
export interface LoginInfo {
|
||||
export interface LoginInfo {
|
||||
token: string
|
||||
endpoint: string
|
||||
email: string
|
||||
@ -27,11 +29,7 @@ export interface LoginInfo {
|
||||
/**
|
||||
* Perform a login operation to required workspace with user credentials.
|
||||
*/
|
||||
export async function doLogin (
|
||||
email: string,
|
||||
password: string,
|
||||
workspace: string
|
||||
): Promise<[Status, LoginInfo | undefined]> {
|
||||
export async function doLogin (email: string, password: string): Promise<[Status, LoginInfo | undefined]> {
|
||||
const accountsUrl = getMetadata(login.metadata.AccountsUrl)
|
||||
|
||||
if (accountsUrl === undefined) {
|
||||
@ -46,9 +44,9 @@ export async function doLogin (
|
||||
}
|
||||
}
|
||||
|
||||
const request: Request<[string, string, string]> = {
|
||||
const request: Request<[string, string]> = {
|
||||
method: 'login',
|
||||
params: [email, password, workspace]
|
||||
params: [email, password]
|
||||
}
|
||||
|
||||
try {
|
||||
@ -67,3 +65,277 @@ export async function doLogin (
|
||||
return [unknownError(err), undefined]
|
||||
}
|
||||
}
|
||||
|
||||
export async function signUp (
|
||||
email: string,
|
||||
password: string,
|
||||
first: string,
|
||||
last: string
|
||||
): Promise<[Status, LoginInfo | undefined]> {
|
||||
const accountsUrl = getMetadata(login.metadata.AccountsUrl)
|
||||
|
||||
if (accountsUrl === undefined) {
|
||||
throw new Error('accounts url not specified')
|
||||
}
|
||||
|
||||
const token = getMetadata(login.metadata.OverrideLoginToken)
|
||||
if (token !== undefined) {
|
||||
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
|
||||
if (endpoint !== undefined) {
|
||||
return [OK, { token, endpoint, email }]
|
||||
}
|
||||
}
|
||||
|
||||
const request: Request<[string, string, string, string]> = {
|
||||
method: 'createAccount',
|
||||
params: [email, password, first, last]
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(accountsUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: serialize(request)
|
||||
})
|
||||
const result: Response<any> = await response.json()
|
||||
return [result.error ?? OK, result.result]
|
||||
} catch (err) {
|
||||
return [unknownError(err), undefined]
|
||||
}
|
||||
}
|
||||
|
||||
export async function createWorkspace (workspace: string): Promise<[Status, LoginInfo | undefined]> {
|
||||
const accountsUrl = getMetadata(login.metadata.AccountsUrl)
|
||||
|
||||
if (accountsUrl === undefined) {
|
||||
throw new Error('accounts url not specified')
|
||||
}
|
||||
|
||||
const overrideToken = getMetadata(login.metadata.OverrideLoginToken)
|
||||
const email = getMetadata(login.metadata.LoginEmail) ?? ''
|
||||
if (overrideToken !== undefined) {
|
||||
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
|
||||
if (endpoint !== undefined) {
|
||||
return [OK, { token: overrideToken, endpoint, email }]
|
||||
}
|
||||
}
|
||||
|
||||
const token = fetchMetadataLocalStorage(login.metadata.LoginToken)
|
||||
if (token === null) {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[1] = 'login'
|
||||
loc.path.length = 2
|
||||
navigate(loc)
|
||||
return [unknownStatus('Please login'), undefined]
|
||||
}
|
||||
|
||||
const request: Request<[string]> = {
|
||||
method: 'createWorkspace',
|
||||
params: [workspace]
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(accountsUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + token,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: serialize(request)
|
||||
})
|
||||
const result: Response<any> = await response.json()
|
||||
return [result.error ?? OK, result.result]
|
||||
} catch (err) {
|
||||
return [unknownError(err), undefined]
|
||||
}
|
||||
}
|
||||
|
||||
export async function getWorkspaces (): Promise<Workspace[]> {
|
||||
const accountsUrl = getMetadata(login.metadata.AccountsUrl)
|
||||
|
||||
if (accountsUrl === undefined) {
|
||||
throw new Error('accounts url not specified')
|
||||
}
|
||||
|
||||
const token = fetchMetadataLocalStorage(login.metadata.LoginToken)
|
||||
if (token === null) {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[1] = 'login'
|
||||
loc.path.length = 2
|
||||
navigate(loc)
|
||||
return []
|
||||
}
|
||||
|
||||
const request: Request<[]> = {
|
||||
method: 'getUserWorkspaces',
|
||||
params: []
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(accountsUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + token,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: serialize(request)
|
||||
})
|
||||
const result: Response<any> = await response.json()
|
||||
console.log(result)
|
||||
return result.result
|
||||
} catch (err) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export async function selectWorkspace (workspace: string): Promise<[Status, LoginInfo | undefined]> {
|
||||
const accountsUrl = getMetadata(login.metadata.AccountsUrl)
|
||||
|
||||
if (accountsUrl === undefined) {
|
||||
throw new Error('accounts url not specified')
|
||||
}
|
||||
|
||||
const token = fetchMetadataLocalStorage(login.metadata.LoginToken)
|
||||
if (token === null) {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[1] = 'login'
|
||||
loc.path.length = 2
|
||||
navigate(loc)
|
||||
return [unknownStatus('Please login'), undefined]
|
||||
}
|
||||
|
||||
const request: Request<[string]> = {
|
||||
method: 'selectWorkspace',
|
||||
params: [workspace]
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(accountsUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + token,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: serialize(request)
|
||||
})
|
||||
const result: Response<any> = await response.json()
|
||||
return [result.error ?? OK, result.result]
|
||||
} catch (err) {
|
||||
return [unknownError(err), undefined]
|
||||
}
|
||||
}
|
||||
|
||||
export async function getWorkspaceHash (): Promise<string> {
|
||||
const accountsUrl = getMetadata(login.metadata.AccountsUrl)
|
||||
|
||||
if (accountsUrl === undefined) {
|
||||
throw new Error('accounts url not specified')
|
||||
}
|
||||
|
||||
const token = fetchMetadataLocalStorage(login.metadata.LoginToken)
|
||||
if (token === null) {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[1] = 'login'
|
||||
loc.path.length = 2
|
||||
navigate(loc)
|
||||
return ''
|
||||
}
|
||||
|
||||
const request: Request<[]> = {
|
||||
method: 'getWorkspaceHash',
|
||||
params: []
|
||||
}
|
||||
|
||||
const response = await fetch(accountsUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + token,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: serialize(request)
|
||||
})
|
||||
const result: Response<any> = await response.json()
|
||||
return result.result
|
||||
}
|
||||
|
||||
export async function join (
|
||||
email: string,
|
||||
password: string,
|
||||
workspaceHash: string
|
||||
): Promise<[Status, LoginInfo | undefined]> {
|
||||
const accountsUrl = getMetadata(login.metadata.AccountsUrl)
|
||||
|
||||
if (accountsUrl === undefined) {
|
||||
throw new Error('accounts url not specified')
|
||||
}
|
||||
|
||||
const token = getMetadata(login.metadata.OverrideLoginToken)
|
||||
if (token !== undefined) {
|
||||
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
|
||||
if (endpoint !== undefined) {
|
||||
return [OK, { token, endpoint, email }]
|
||||
}
|
||||
}
|
||||
|
||||
const request: Request<[string, string, string]> = {
|
||||
method: 'join',
|
||||
params: [email, password, workspaceHash]
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(accountsUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: serialize(request)
|
||||
})
|
||||
const result: Response<any> = await response.json()
|
||||
return [result.error ?? OK, result.result]
|
||||
} catch (err) {
|
||||
return [unknownError(err), undefined]
|
||||
}
|
||||
}
|
||||
|
||||
export async function signUpJoin (
|
||||
email: string,
|
||||
password: string,
|
||||
first: string,
|
||||
last: string,
|
||||
workspaceHash: string
|
||||
): Promise<[Status, LoginInfo | undefined]> {
|
||||
const accountsUrl = getMetadata(login.metadata.AccountsUrl)
|
||||
|
||||
if (accountsUrl === undefined) {
|
||||
throw new Error('accounts url not specified')
|
||||
}
|
||||
|
||||
const token = getMetadata(login.metadata.OverrideLoginToken)
|
||||
if (token !== undefined) {
|
||||
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
|
||||
if (endpoint !== undefined) {
|
||||
return [OK, { token, endpoint, email }]
|
||||
}
|
||||
}
|
||||
|
||||
const request: Request<[string, string, string, string, string]> = {
|
||||
method: 'signUpJoin',
|
||||
params: [email, password, first, last, workspaceHash]
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(accountsUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: serialize(request)
|
||||
})
|
||||
const result: Response<any> = await response.json()
|
||||
return [result.error ?? OK, result.result]
|
||||
} catch (err) {
|
||||
return [unknownError(err), undefined]
|
||||
}
|
||||
}
|
||||
|
@ -24,3 +24,20 @@ spec:
|
||||
secretKeyRef:
|
||||
name: mongodb
|
||||
key: url
|
||||
- name: TRANSACTOR_URL
|
||||
value: ws://transactor/
|
||||
- name: MINIO_ENDPOINT
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: minio
|
||||
key: endpoint
|
||||
- name: MINIO_ACCESS_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: minio
|
||||
key: accessKey
|
||||
- name: MINIO_SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: minio
|
||||
key: secretKey
|
||||
|
@ -12,10 +12,12 @@
|
||||
"bundle": "esbuild src/index.ts --bundle --minify --platform=node > bundle.js",
|
||||
"docker:build": "docker build -t anticrm/account .",
|
||||
"docker:push": "docker push anticrm/account",
|
||||
"run-local": "cross-env MONGO_URL=mongodb://localhost:27017 MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin MINIO_ENDPOINT=localhost TRANSACTOR_URL=ws:/localhost:3333 ts-node src/index.ts",
|
||||
"lint": "eslint src",
|
||||
"format": "prettier --write src && eslint --fix src"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.3",
|
||||
"@anticrm/platform-rig": "~0.6.0",
|
||||
"@types/heft-jest": "^1.0.2",
|
||||
"@types/node": "^16.4.10",
|
||||
@ -32,6 +34,7 @@
|
||||
"@typescript-eslint/parser": "^5.4.0",
|
||||
"eslint-config-standard-with-typescript": "^21.0.1",
|
||||
"prettier": "^2.4.1",
|
||||
"ts-node": "^10.2.1",
|
||||
"@rushstack/heft": "^0.41.1",
|
||||
"typescript": "^4.3.5"
|
||||
},
|
||||
|
@ -17,6 +17,7 @@
|
||||
import accountPlugin, { ACCOUNT_DB, methods } from '@anticrm/account'
|
||||
import platform, { Request, Response, serialize, setMetadata, Severity, Status } from '@anticrm/platform'
|
||||
import cors from '@koa/cors'
|
||||
import { IncomingHttpHeaders } from 'http'
|
||||
import Koa from 'koa'
|
||||
import bodyParser from 'koa-bodyparser'
|
||||
import Router from 'koa-router'
|
||||
@ -41,9 +42,21 @@ let client: MongoClient
|
||||
const app = new Koa()
|
||||
const router = new Router()
|
||||
|
||||
const extractToken = (header: IncomingHttpHeaders): string | undefined => {
|
||||
try {
|
||||
return header.authorization?.slice(7) ?? undefined
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
router.post('rpc', '/', async (ctx) => {
|
||||
const token = extractToken(ctx.request.headers)
|
||||
|
||||
const request = ctx.request.body
|
||||
const method = (methods as { [key: string]: (db: Db, request: Request<any>) => Response<any> })[request.method]
|
||||
const method = (methods as { [key: string]: (db: Db, request: Request<any>, token?: string) => Response<any> })[
|
||||
request.method
|
||||
]
|
||||
if (method === undefined) {
|
||||
const response: Response<void> = {
|
||||
id: request.id,
|
||||
@ -57,7 +70,7 @@ router.post('rpc', '/', async (ctx) => {
|
||||
client = await MongoClient.connect(dbUri)
|
||||
}
|
||||
const db = client.db(ACCOUNT_DB)
|
||||
const result = await method(db, request)
|
||||
const result = await method(db, request, token)
|
||||
console.log(result)
|
||||
ctx.body = result
|
||||
})
|
||||
|
@ -23,11 +23,18 @@
|
||||
"eslint-config-standard-with-typescript": "^21.0.1",
|
||||
"prettier": "^2.4.1",
|
||||
"@rushstack/heft": "^0.41.1",
|
||||
"typescript": "^4.3.5"
|
||||
"typescript": "^4.3.5",
|
||||
"@types/minio": "^7.0.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"mongodb": "^4.1.1",
|
||||
"@anticrm/platform": "~0.6.5",
|
||||
"@anticrm/model-all": "~0.6.0",
|
||||
"minio": "^7.0.19",
|
||||
"@anticrm/core": "~0.6.14",
|
||||
"@anticrm/contact": "~0.6.2",
|
||||
"@anticrm/client-resources": "~0.6.4",
|
||||
"@anticrm/client": "~0.6.1",
|
||||
"jwt-simple": "~0.5.6"
|
||||
}
|
||||
}
|
||||
|
36
server/account/src/connect.ts
Normal file
36
server/account/src/connect.ts
Normal file
@ -0,0 +1,36 @@
|
||||
//
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021, 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import client from '@anticrm/client'
|
||||
import clientResources from '@anticrm/client-resources'
|
||||
import { Client } from '@anticrm/core'
|
||||
import { getMetadata, setMetadata } from '@anticrm/platform'
|
||||
import { encode } from 'jwt-simple'
|
||||
import accountPlugin from '.'
|
||||
|
||||
// eslint-disable-next-line
|
||||
const WebSocket = require('ws')
|
||||
|
||||
export async function connect (transactorUrl: string, workspace: string, email?: string): Promise<Client> {
|
||||
const token = encode(
|
||||
{ email: email ?? 'anticrm@hc.engineering', workspace },
|
||||
getMetadata(accountPlugin.metadata.Secret) ?? 'secret'
|
||||
)
|
||||
|
||||
// We need to override default factory with 'ws' one.
|
||||
setMetadata(client.metadata.ClientSocketFactory, (url) => new WebSocket(url))
|
||||
return await (await clientResources()).function.GetClient(token, transactorUrl)
|
||||
}
|
@ -14,10 +14,25 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import platform, { getMetadata, Metadata, Plugin, Request, Response, PlatformError, plugin, Severity, Status, StatusCode } from '@anticrm/platform'
|
||||
import platform, {
|
||||
getMetadata,
|
||||
Metadata,
|
||||
Plugin,
|
||||
Request,
|
||||
Response,
|
||||
PlatformError,
|
||||
plugin,
|
||||
Severity,
|
||||
Status,
|
||||
StatusCode
|
||||
} from '@anticrm/platform'
|
||||
import { pbkdf2Sync, randomBytes } from 'crypto'
|
||||
import { encode } from 'jwt-simple'
|
||||
import { decode, encode } from 'jwt-simple'
|
||||
import { Binary, Db, ObjectId } from 'mongodb'
|
||||
import core, { TxOperations } from '@anticrm/core'
|
||||
import contact, { combineName } from '@anticrm/contact'
|
||||
import { connect } from './connect'
|
||||
import { initWorkspace } from './tool'
|
||||
|
||||
const WORKSPACE_COLLECTION = 'workspace'
|
||||
const ACCOUNT_COLLECTION = 'account'
|
||||
@ -41,11 +56,11 @@ const accountPlugin = plugin(accountId, {
|
||||
Secret: '' as Metadata<string>
|
||||
},
|
||||
status: {
|
||||
AccountNotFound: '' as StatusCode<{account: string}>,
|
||||
WorkspaceNotFound: '' as StatusCode<{workspace: string}>,
|
||||
InvalidPassword: '' as StatusCode<{account: string}>,
|
||||
AccountAlreadyExists: '' as StatusCode<{account: string}>,
|
||||
WorkspaceAlreadyExists: '' as StatusCode<{workspace: string}>
|
||||
AccountNotFound: '' as StatusCode<{ account: string }>,
|
||||
WorkspaceNotFound: '' as StatusCode<{ workspace: string }>,
|
||||
InvalidPassword: '' as StatusCode<{ account: string }>,
|
||||
AccountAlreadyExists: '' as StatusCode<{ account: string }>,
|
||||
WorkspaceAlreadyExists: '' as StatusCode<{ workspace: string }>
|
||||
}
|
||||
})
|
||||
|
||||
@ -150,8 +165,25 @@ function generateToken (email: string, workspace: string): string {
|
||||
* @param workspace -
|
||||
* @returns
|
||||
*/
|
||||
export async function login (db: Db, email: string, password: string, workspace: string): Promise<LoginInfo> {
|
||||
const accountInfo = await getAccountInfo(db, email, password)
|
||||
export async function login (db: Db, email: string, password: string): Promise<LoginInfo> {
|
||||
await getAccountInfo(db, email, password)
|
||||
const result = {
|
||||
endpoint: getEndpoint(),
|
||||
email,
|
||||
token: generateToken(email, '')
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function selectWorkspace (db: Db, token: string, workspace: string): Promise<LoginInfo> {
|
||||
const { email } = decode(token, getSecret())
|
||||
const accountInfo = await getAccount(db, email)
|
||||
if (accountInfo === null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.AccountNotFound, { account: email }))
|
||||
}
|
||||
const workspaceInfo = await getWorkspace(db, workspace)
|
||||
|
||||
if (workspaceInfo !== null) {
|
||||
@ -175,7 +207,43 @@ export async function login (db: Db, email: string, password: string, workspace:
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function createAccount (db: Db, email: string, password: string, first: string, last: string): Promise<AccountInfo> {
|
||||
export async function join (db: Db, email: string, password: string, workspaceHash: string): Promise<LoginInfo> {
|
||||
const workspace = decode(workspaceHash, getSecret())
|
||||
const token = (await login(db, email, password)).token
|
||||
await assignWorkspace(db, email, workspace)
|
||||
|
||||
return await selectWorkspace(db, token, workspace)
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function signUpJoin (
|
||||
db: Db,
|
||||
email: string,
|
||||
password: string,
|
||||
first: string,
|
||||
last: string,
|
||||
workspaceHash: string
|
||||
): Promise<LoginInfo> {
|
||||
await createAccount(db, email, password, first, last)
|
||||
const workspace = decode(workspaceHash, getSecret())
|
||||
await assignWorkspace(db, email, workspace)
|
||||
|
||||
const token = (await login(db, email, password)).token
|
||||
return await selectWorkspace(db, token, workspace)
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function createAccount (
|
||||
db: Db,
|
||||
email: string,
|
||||
password: string,
|
||||
first: string,
|
||||
last: string
|
||||
): Promise<LoginInfo> {
|
||||
const salt = randomBytes(32)
|
||||
const hash = hashWithSalt(password, salt)
|
||||
|
||||
@ -184,7 +252,7 @@ export async function createAccount (db: Db, email: string, password: string, fi
|
||||
throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.AccountAlreadyExists, { account: email }))
|
||||
}
|
||||
|
||||
const insert = await db.collection(ACCOUNT_COLLECTION).insertOne({
|
||||
await db.collection(ACCOUNT_COLLECTION).insertOne({
|
||||
email,
|
||||
hash,
|
||||
salt,
|
||||
@ -193,31 +261,26 @@ export async function createAccount (db: Db, email: string, password: string, fi
|
||||
workspaces: []
|
||||
})
|
||||
|
||||
return {
|
||||
_id: insert.insertedId,
|
||||
first,
|
||||
last,
|
||||
const result = {
|
||||
endpoint: getEndpoint(),
|
||||
email,
|
||||
workspaces: []
|
||||
token: generateToken(email, '')
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function listWorkspaces (db: Db): Promise<Workspace[]> {
|
||||
return await db.collection<Workspace>(WORKSPACE_COLLECTION)
|
||||
.find({})
|
||||
.toArray()
|
||||
return await db.collection<Workspace>(WORKSPACE_COLLECTION).find({}).toArray()
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function listAccounts (db: Db): Promise<Account[]> {
|
||||
return await db.collection<Account>(ACCOUNT_COLLECTION)
|
||||
.find({})
|
||||
.toArray()
|
||||
return await db.collection<Account>(ACCOUNT_COLLECTION).find({}).toArray()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -227,16 +290,64 @@ export async function createWorkspace (db: Db, workspace: string, organisation:
|
||||
if ((await getWorkspace(db, workspace)) !== null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.WorkspaceAlreadyExists, { workspace }))
|
||||
}
|
||||
return await db
|
||||
const result = await db
|
||||
.collection(WORKSPACE_COLLECTION)
|
||||
.insertOne({
|
||||
workspace,
|
||||
organisation
|
||||
})
|
||||
.then((e) => e.insertedId.toHexString())
|
||||
await initWorkspace(getEndpoint(), workspace)
|
||||
return result
|
||||
}
|
||||
|
||||
async function getWorkspaceAndAccount (db: Db, email: string, workspace: string): Promise<{ accountId: ObjectId, workspaceId: ObjectId }> {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function createUserWorkspace (db: Db, token: string, workspace: string): Promise<LoginInfo> {
|
||||
const { email } = decode(token, getSecret())
|
||||
await createWorkspace(db, workspace, '')
|
||||
await assignWorkspace(db, email, workspace)
|
||||
const result = {
|
||||
endpoint: getEndpoint(),
|
||||
email,
|
||||
token: generateToken(email, workspace)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function getWorkspaceHash (db: Db, token: string): Promise<string> {
|
||||
const { workspace } = decode(token, getSecret())
|
||||
const wsPromise = await getWorkspace(db, workspace)
|
||||
if (wsPromise === null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.WorkspaceNotFound, { workspace }))
|
||||
}
|
||||
return encode(workspace, getSecret())
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function getUserWorkspaces (db: Db, token: string): Promise<Workspace[]> {
|
||||
const { email } = decode(token, getSecret())
|
||||
const account = await getAccount(db, email)
|
||||
if (account === null) return []
|
||||
return await db
|
||||
.collection<Workspace>(WORKSPACE_COLLECTION)
|
||||
.find({
|
||||
_id: { $in: account.workspaces }
|
||||
})
|
||||
.toArray()
|
||||
}
|
||||
|
||||
async function getWorkspaceAndAccount (
|
||||
db: Db,
|
||||
email: string,
|
||||
workspace: string
|
||||
): Promise<{ accountId: ObjectId, workspaceId: ObjectId }> {
|
||||
const wsPromise = await getWorkspace(db, workspace)
|
||||
if (wsPromise === null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.WorkspaceNotFound, { workspace }))
|
||||
@ -260,6 +371,30 @@ export async function assignWorkspace (db: Db, email: string, workspace: string)
|
||||
|
||||
// Add workspace to account
|
||||
await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: accountId }, { $push: { workspaces: workspaceId } })
|
||||
|
||||
const account = await db.collection<Account>(ACCOUNT_COLLECTION).findOne({ _id: accountId })
|
||||
|
||||
if (account !== null) await createEmployeeAccount(account, workspace)
|
||||
}
|
||||
|
||||
async function createEmployeeAccount (account: Account, workspace: string): Promise<void> {
|
||||
const connection = await connect(getEndpoint(), workspace, account.email)
|
||||
const ops = new TxOperations(connection, core.account.System)
|
||||
|
||||
const name = combineName(account.first, account.last)
|
||||
|
||||
const employee = await ops.createDoc(contact.class.Employee, contact.space.Employee, {
|
||||
name,
|
||||
city: '',
|
||||
channels: []
|
||||
})
|
||||
|
||||
await ops.createDoc(contact.class.EmployeeAccount, core.space.Model, {
|
||||
email: account.email,
|
||||
employee,
|
||||
name
|
||||
})
|
||||
await connection.close()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -284,7 +419,9 @@ export async function dropWorkspace (db: Db, workspace: string): Promise<void> {
|
||||
throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.WorkspaceNotFound, { workspace }))
|
||||
}
|
||||
await db.collection(WORKSPACE_COLLECTION).deleteOne({ _id: ws._id })
|
||||
await db.collection<Account>(ACCOUNT_COLLECTION).updateMany({ _id: { $in: ws.accounts } }, { $pull: { workspaces: ws._id } })
|
||||
await db
|
||||
.collection<Account>(ACCOUNT_COLLECTION)
|
||||
.updateMany({ _id: { $in: ws.accounts } }, { $pull: { workspaces: ws._id } })
|
||||
}
|
||||
|
||||
/**
|
||||
@ -296,14 +433,22 @@ export async function dropAccount (db: Db, email: string): Promise<void> {
|
||||
throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.AccountNotFound, { account: email }))
|
||||
}
|
||||
await db.collection(ACCOUNT_COLLECTION).deleteOne({ _id: account._id })
|
||||
await db.collection<Workspace>(WORKSPACE_COLLECTION).updateMany({ _id: { $in: account.workspaces } }, { $pull: { accounts: account._id } })
|
||||
await db
|
||||
.collection<Workspace>(WORKSPACE_COLLECTION)
|
||||
.updateMany({ _id: { $in: account.workspaces } }, { $pull: { accounts: account._id } })
|
||||
}
|
||||
|
||||
function wrap (f: (db: Db, ...args: any[]) => Promise<any>) {
|
||||
return async function (db: Db, request: Request<any[]>): Promise<Response<any>> {
|
||||
return async function (db: Db, request: Request<any[]>, token?: string): Promise<Response<any>> {
|
||||
if (token !== undefined) request.params.unshift(token)
|
||||
return await f(db, ...request.params)
|
||||
.then((result) => ({ id: request.id, result }))
|
||||
.catch((err) => ({ error: err instanceof PlatformError ? new Status(Severity.ERROR, platform.status.Forbidden, {}) : new Status(Severity.ERROR, platform.status.InternalServerError, {}) }))
|
||||
.catch((err) => ({
|
||||
error:
|
||||
err instanceof PlatformError
|
||||
? new Status(Severity.ERROR, platform.status.Forbidden, {})
|
||||
: new Status(Severity.ERROR, platform.status.InternalServerError, {})
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@ -312,9 +457,14 @@ function wrap (f: (db: Db, ...args: any[]) => Promise<any>) {
|
||||
*/
|
||||
export const methods = {
|
||||
login: wrap(login),
|
||||
join: wrap(join),
|
||||
signUpJoin: wrap(signUpJoin),
|
||||
selectWorkspace: wrap(selectWorkspace),
|
||||
getUserWorkspaces: wrap(getUserWorkspaces),
|
||||
getWorkspaceHash: wrap(getWorkspaceHash),
|
||||
getAccountInfo: wrap(getAccountInfo),
|
||||
createAccount: wrap(createAccount),
|
||||
createWorkspace: wrap(createWorkspace),
|
||||
createWorkspace: wrap(createUserWorkspace),
|
||||
assignWorkspace: wrap(assignWorkspace),
|
||||
removeWorkspace: wrap(removeWorkspace),
|
||||
listWorkspaces: wrap(listWorkspaces)
|
||||
|
87
server/account/src/tool.ts
Normal file
87
server/account/src/tool.ts
Normal file
@ -0,0 +1,87 @@
|
||||
//
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021, 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import core, { DOMAIN_TX, Tx } from '@anticrm/core'
|
||||
import builder, { createDeps } from '@anticrm/model-all'
|
||||
import { Client } from 'minio'
|
||||
import { Document, MongoClient } from 'mongodb'
|
||||
import { connect } from './connect'
|
||||
|
||||
export async function initWorkspace (transactorUrl: string, dbName: string): Promise<void> {
|
||||
const minioEndpoint = process.env.MINIO_ENDPOINT
|
||||
if (minioEndpoint === undefined) {
|
||||
console.error('please provide minio endpoint')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const minioAccessKey = process.env.MINIO_ACCESS_KEY
|
||||
if (minioAccessKey === undefined) {
|
||||
console.error('please provide minio access key')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const minioSecretKey = process.env.MINIO_SECRET_KEY
|
||||
if (minioSecretKey === undefined) {
|
||||
console.error('please provide minio secret key')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const mongodbUri = process.env.MONGO_URL
|
||||
if (mongodbUri === undefined) {
|
||||
console.error('please provide mongodb url.')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const minio = new Client({
|
||||
endPoint: minioEndpoint,
|
||||
port: 9000,
|
||||
useSSL: false,
|
||||
accessKey: minioAccessKey,
|
||||
secretKey: minioSecretKey
|
||||
})
|
||||
|
||||
const txes = JSON.parse(JSON.stringify(builder.getTxes())) as Tx[]
|
||||
|
||||
if (txes.some((tx) => tx.objectSpace !== core.space.Model)) {
|
||||
throw Error('Model txes must target only core.space.Model')
|
||||
}
|
||||
|
||||
const client = new MongoClient(mongodbUri)
|
||||
try {
|
||||
await client.connect()
|
||||
const db = client.db(dbName)
|
||||
|
||||
console.log('dropping database...')
|
||||
await db.dropDatabase()
|
||||
|
||||
console.log('creating model...')
|
||||
const model = txes
|
||||
const result = await db.collection(DOMAIN_TX).insertMany(model as Document[])
|
||||
console.log(`${result.insertedCount} model transactions inserted.`)
|
||||
|
||||
console.log('creating data...')
|
||||
const connection = await connect(transactorUrl, dbName)
|
||||
await createDeps(connection)
|
||||
await connection.close()
|
||||
|
||||
console.log('create minio bucket')
|
||||
if (!(await minio.bucketExists(dbName))) {
|
||||
await minio.makeBucket(dbName, 'k8s')
|
||||
}
|
||||
} finally {
|
||||
await client.close()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user