From 5fd5658fb386899dddef5a839ce06c8593414cbe Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Fri, 16 Jun 2023 13:10:09 +0700 Subject: [PATCH] UBER-496: Fix few issues Signed-off-by: Andrey Sobolev --- .vscode/launch.json | 5 +- models/contact/src/index.ts | 3 + models/contact/src/plugin.ts | 1 + models/recruit/src/index.ts | 2 +- models/tracker/src/index.ts | 90 ++++++++++++++++--- models/tracker/src/plugin.ts | 8 +- models/workbench/src/plugin.ts | 7 +- .../profiles/assets/config/heft.json | 12 +++ .../profiles/default/config/heft.json | 12 +++ packages/platform/src/lang/en.json | 1 + packages/platform/src/lang/ru.json | 1 + packages/platform/src/platform.ts | 1 + .../EmployeeAccountPresenter.svelte | 2 +- .../EmployeeAccountRefPresenter.svelte | 2 +- plugins/contact-resources/src/index.ts | 7 +- .../src/components/EditFunnel.svelte | 3 +- .../src/components/MyLeads.svelte | 9 +- plugins/login-assets/lang/en.json | 2 +- plugins/login-assets/lang/ru.json | 2 +- .../src/components/CreateWorkspaceForm.svelte | 2 +- .../src/components/RequestActions.svelte | 2 +- plugins/templates-assets/lang/ru.json | 2 +- plugins/tracker-assets/lang/en.json | 5 +- plugins/tracker-assets/lang/ru.json | 5 +- plugins/tracker-resources/src/index.ts | 15 +++- plugins/tracker-resources/src/plugin.ts | 9 +- .../src/components/SpecialView.svelte | 7 +- .../src/components/navigator/SpacesNav.svelte | 28 +++++- plugins/workbench-resources/src/index.ts | 5 +- plugins/workbench/src/index.ts | 2 + pods/account/src/index.ts | 1 - server/account/src/index.ts | 58 +++++++++++- server/minio/src/index.ts | 27 +++--- 33 files changed, 277 insertions(+), 61 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index f401a8467b..6b8e14ab4b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -69,7 +69,10 @@ "TRANSACTOR_URL": "ws:/localhost:3333", "ACCOUNT_PORT": "3000", "FRONT_URL": "http://localhost:8080", - "SES_URL": "http://localhost:8091" + "SES_URL": "http://localhost:8091", + "MINIO_ACCESS_KEY": "minioadmin", + "MINIO_SECRET_KEY": "minioadmin", + "MINIO_ENDPOINT": "localhost" }, "runtimeArgs": ["--nolazy", "-r", "ts-node/register"], "sourceMaps": true, diff --git a/models/contact/src/index.ts b/models/contact/src/index.ts index 19dec532b9..bee661a227 100644 --- a/models/contact/src/index.ts +++ b/models/contact/src/index.ts @@ -627,6 +627,9 @@ export function createModel (builder: Builder): void { builder.mixin(core.class.Account, core.class.Class, view.mixin.ObjectPresenter, { presenter: contact.component.EmployeeAccountPresenter }) + builder.mixin(core.class.Account, core.class.Class, view.mixin.AttributePresenter, { + presenter: contact.component.EmployeeAccountRefPresenter + }) builder.mixin(contact.class.Organization, core.class.Class, view.mixin.ObjectPresenter, { presenter: contact.component.OrganizationPresenter diff --git a/models/contact/src/plugin.ts b/models/contact/src/plugin.ts index 2c7c9790ea..f5a435d1f3 100644 --- a/models/contact/src/plugin.ts +++ b/models/contact/src/plugin.ts @@ -36,6 +36,7 @@ export default mergeIds(contactId, contact, { Contacts: '' as AnyComponent, ContactsTabs: '' as AnyComponent, EmployeeAccountPresenter: '' as AnyComponent, + EmployeeAccountRefPresenter: '' as AnyComponent, OrganizationEditor: '' as AnyComponent, EmployeePresenter: '' as AnyComponent, EmployeeRefPresenter: '' as AnyComponent, diff --git a/models/recruit/src/index.ts b/models/recruit/src/index.ts index a65c0b524a..f11acc09d1 100644 --- a/models/recruit/src/index.ts +++ b/models/recruit/src/index.ts @@ -651,7 +651,7 @@ export function createModel (builder: Builder): void { const applicantViewOptions = (colors: boolean): ViewOptionsModel => { const model: ViewOptionsModel = { - groupBy: ['state', 'assignee', 'space'], + groupBy: ['state', 'assignee', 'space', 'createdBy', 'modifiedBy'], orderBy: [ ['state', SortingOrder.Ascending], ['modifiedOn', SortingOrder.Descending], diff --git a/models/tracker/src/index.ts b/models/tracker/src/index.ts index 947b93738a..5a63f400c6 100644 --- a/models/tracker/src/index.ts +++ b/models/tracker/src/index.ts @@ -70,7 +70,7 @@ import { TimeSpendReport, trackerId } from '@hcengineering/tracker' -import { KeyBinding, ViewOptionsModel } from '@hcengineering/view' +import { KeyBinding, ViewAction, ViewOptionsModel } from '@hcengineering/view' import tracker from './plugin' import { generateClassNotificationTypes } from '@hcengineering/model-notification' @@ -385,7 +385,7 @@ export function createModel (builder: Builder): void { ) const issuesOptions = (kanban: boolean): ViewOptionsModel => ({ - groupBy: ['status', 'assignee', 'priority', 'component', 'milestone'], + groupBy: ['status', 'assignee', 'priority', 'component', 'milestone', 'createdBy', 'modifiedBy'], orderBy: [ ['status', SortingOrder.Ascending], ['priority', SortingOrder.Descending], @@ -554,7 +554,7 @@ export function createModel (builder: Builder): void { ) const subIssuesOptions: ViewOptionsModel = { - groupBy: ['status', 'assignee', 'priority', 'milestone'], + groupBy: ['status', 'assignee', 'priority', 'milestone', 'createdBy', 'modifiedBy'], orderBy: [ ['rank', SortingOrder.Ascending], ['status', SortingOrder.Ascending], @@ -667,7 +667,7 @@ export function createModel (builder: Builder): void { attachTo: tracker.class.IssueTemplate, descriptor: view.viewlet.List, viewOptions: { - groupBy: ['assignee', 'priority', 'component', 'milestone'], + groupBy: ['assignee', 'priority', 'component', 'milestone', 'createdBy', 'modifiedBy'], orderBy: [ ['priority', SortingOrder.Ascending], ['modifiedOn', SortingOrder.Descending], @@ -1060,6 +1060,20 @@ export function createModel (builder: Builder): void { ['backlog', tracker.string.Backlog, {}] ] } + }, + { + id: 'all-projects', + component: workbench.component.SpecialView, + icon: view.icon.Archive, + label: tracker.string.AllProjects, + position: 'bottom', + visibleIf: workbench.function.IsOwner, + spaceClass: tracker.class.Project, + componentProps: { + _class: tracker.class.Project, + label: tracker.string.AllIssues, + icon: tracker.icon.Issues + } } ], spaces: [ @@ -1068,6 +1082,7 @@ export function createModel (builder: Builder): void { spaceClass: tracker.class.Project, addSpaceLabel: tracker.string.CreateProject, createComponent: tracker.component.CreateProject, + visibleIf: tracker.function.IsProjectJoined, icon: tracker.icon.Home, specials: [ { @@ -1162,9 +1177,7 @@ export function createModel (builder: Builder): void { input: 'focus', category: tracker.category.Tracker, target: tracker.class.Project, - query: { - archived: false - }, + query: {}, context: { mode: ['context', 'browser'], group: 'edit' @@ -1182,9 +1195,7 @@ export function createModel (builder: Builder): void { input: 'focus', category: tracker.category.Tracker, target: tracker.class.Project, - query: { - archived: false - }, + query: {}, context: { mode: ['context', 'browser'], group: 'edit' @@ -1213,6 +1224,28 @@ export function createModel (builder: Builder): void { }, tracker.action.DeleteProject ) + createAction(builder, { + label: tracker.string.Unarchive, + icon: view.icon.Archive, + action: view.actionImpl.UpdateDocument as ViewAction, + actionProps: { + key: 'archived', + ask: true, + value: false, + label: tracker.string.Unarchive, + message: tracker.string.UnarchiveConfirm + }, + input: 'any', + category: tracker.category.Tracker, + query: { + archived: true + }, + context: { + mode: ['context', 'browser'], + group: 'tools' + }, + target: tracker.class.Project + }) createAction( builder, @@ -1391,6 +1424,10 @@ export function createModel (builder: Builder): void { override: [view.action.Open] }) + builder.mixin(tracker.class.Project, core.class.Class, view.mixin.IgnoreActions, { + actions: [view.action.Open] + }) + builder.mixin(tracker.class.Issue, core.class.Class, view.mixin.ClassFilters, { filters: [ 'status', @@ -1852,7 +1889,7 @@ export function createModel (builder: Builder): void { ) const milestoneOptions: ViewOptionsModel = { - groupBy: ['status'], + groupBy: ['status', 'createdBy', 'modifiedBy'], orderBy: [ ['modifiedOn', SortingOrder.Descending], ['targetDate', SortingOrder.Descending], @@ -1934,7 +1971,7 @@ export function createModel (builder: Builder): void { ) const componentListViewOptions: ViewOptionsModel = { - groupBy: ['lead'], + groupBy: ['lead', 'createdBy', 'modifiedBy'], orderBy: [ ['modifiedOn', SortingOrder.Descending], ['createdOn', SortingOrder.Descending] @@ -1973,4 +2010,33 @@ export function createModel (builder: Builder): void { }, tracker.viewlet.ComponentList ) + + builder.createDoc( + view.class.Viewlet, + core.space.Model, + { + attachTo: tracker.class.Project, + descriptor: view.viewlet.List, + viewOptions: { + groupBy: ['createdBy'], + orderBy: [ + ['modifiedOn', SortingOrder.Descending], + ['createdOn', SortingOrder.Descending] + ], + other: [showColorsViewOption] + }, + configOptions: { + strict: true, + hiddenKeys: ['label', 'description'] + }, + config: [ + { + key: '', + props: { kind: 'list' } + }, + { key: '', displayProps: { grow: true } } + ] + }, + tracker.viewlet.ProjectList + ) } diff --git a/models/tracker/src/plugin.ts b/models/tracker/src/plugin.ts index 527dc75d5e..9551d3725e 100644 --- a/models/tracker/src/plugin.ts +++ b/models/tracker/src/plugin.ts @@ -40,7 +40,10 @@ export default mergeIds(trackerId, tracker, { CreatedDate: '' as IntlString, ChangeStatus: '' as IntlString, ConfigLabel: '' as IntlString, - ConfigDescription: '' as IntlString + ConfigDescription: '' as IntlString, + Unarchive: '' as IntlString, + UnarchiveConfirm: '' as IntlString, + AllProjects: '' as IntlString }, activity: { TxIssueCreated: '' as AnyComponent, @@ -62,7 +65,8 @@ export default mergeIds(trackerId, tracker, { IssueTemplateList: '' as Ref, IssueKanban: '' as Ref, MilestoneList: '' as Ref, - ComponentList: '' as Ref + ComponentList: '' as Ref, + ProjectList: '' as Ref }, ids: { TxIssueCreated: '' as Ref, diff --git a/models/workbench/src/plugin.ts b/models/workbench/src/plugin.ts index 8f406bc511..7ca022ed3f 100644 --- a/models/workbench/src/plugin.ts +++ b/models/workbench/src/plugin.ts @@ -13,8 +13,8 @@ // limitations under the License. // -import { Space } from '@hcengineering/core' -import { IntlString, mergeIds, Resource } from '@hcengineering/platform' +import { Doc, Space } from '@hcengineering/core' +import { IntlString, Resource, mergeIds } from '@hcengineering/platform' import { AnyComponent } from '@hcengineering/ui' import { workbenchId } from '@hcengineering/workbench' import workbench from '@hcengineering/workbench-resources/src/plugin' @@ -32,6 +32,7 @@ export default mergeIds(workbenchId, workbench, { HiddenApplication: '' as IntlString }, function: { - HasArchiveSpaces: '' as Resource<(spaces: Space[]) => Promise> + HasArchiveSpaces: '' as Resource<(spaces: Space[]) => Promise>, + IsOwner: '' as Resource<(docs: Doc[]) => Promise> } }) diff --git a/packages/platform-rig/profiles/assets/config/heft.json b/packages/platform-rig/profiles/assets/config/heft.json index 7b2cf24e39..d81257478f 100644 --- a/packages/platform-rig/profiles/assets/config/heft.json +++ b/packages/platform-rig/profiles/assets/config/heft.json @@ -7,6 +7,18 @@ "heftEvent": "clean", "actionId": "defaultClean", "globsToDelete": ["dist", "lib", "temp"] + }, + { + "actionKind": "copyFiles", + "heftEvent": "pre-compile", + "actionId": "copy-lang", + "copyOperations": [ + { + "sourceFolder": "src", + "destinationFolders": ["lib"], + "includeGlobs": ["lang"] + } + ] } ], "heftPlugins": [ diff --git a/packages/platform-rig/profiles/default/config/heft.json b/packages/platform-rig/profiles/default/config/heft.json index 7b2cf24e39..d81257478f 100644 --- a/packages/platform-rig/profiles/default/config/heft.json +++ b/packages/platform-rig/profiles/default/config/heft.json @@ -7,6 +7,18 @@ "heftEvent": "clean", "actionId": "defaultClean", "globsToDelete": ["dist", "lib", "temp"] + }, + { + "actionKind": "copyFiles", + "heftEvent": "pre-compile", + "actionId": "copy-lang", + "copyOperations": [ + { + "sourceFolder": "src", + "destinationFolders": ["lib"], + "includeGlobs": ["lang"] + } + ] } ], "heftPlugins": [ diff --git a/packages/platform/src/lang/en.json b/packages/platform/src/lang/en.json index 16f2908a27..6678fbaf40 100644 --- a/packages/platform/src/lang/en.json +++ b/packages/platform/src/lang/en.json @@ -14,6 +14,7 @@ "WorkspaceNotFound": "Workspace not found", "InvalidPassword": "Invalid password", "AccountAlreadyExists": "Account already exists", + "WorkspaceRateLimit": "Server is busy, Please wait a bit and try again", "AccountAlreadyConfirmed": "Account already confirmed", "AccountWasMerged": "Account was merged", "WorkspaceAlreadyExists": "Workspace already exists", diff --git a/packages/platform/src/lang/ru.json b/packages/platform/src/lang/ru.json index b7e043aaa8..a84654b002 100644 --- a/packages/platform/src/lang/ru.json +++ b/packages/platform/src/lang/ru.json @@ -14,6 +14,7 @@ "WorkspaceNotFound": "Рабочее пространство не найдено", "InvalidPassword": "Неверный пароль", "AccountAlreadyExists": "Аккаунт уже существует", + "WorkspaceRateLimit": "Сервер перегружен, Пожалуйста подождите", "AccountAlreadyConfirmed": "Аккаунт уже подтвержден", "AccountWasMerged": "Аккаунт был объединен", "WorkspaceAlreadyExists": "Рабочее пространство уже существует", diff --git a/packages/platform/src/platform.ts b/packages/platform/src/platform.ts index 6e8929140c..8db4f0c7da 100644 --- a/packages/platform/src/platform.ts +++ b/packages/platform/src/platform.ts @@ -152,6 +152,7 @@ export default plugin(platformId, { AccountAlreadyConfirmed: '' as StatusCode<{ account: string }>, AccountWasMerged: '' as StatusCode<{ account: string }>, WorkspaceAlreadyExists: '' as StatusCode<{ workspace: string }>, + WorkspaceRateLimit: '' as StatusCode<{ workspace: string }>, ProductIdMismatch: '' as StatusCode<{ productId: string }> }, metadata: { diff --git a/plugins/contact-resources/src/components/EmployeeAccountPresenter.svelte b/plugins/contact-resources/src/components/EmployeeAccountPresenter.svelte index f8ce3a5c62..2029d17aa6 100644 --- a/plugins/contact-resources/src/components/EmployeeAccountPresenter.svelte +++ b/plugins/contact-resources/src/components/EmployeeAccountPresenter.svelte @@ -36,7 +36,7 @@ {#if value} {#if employee} - + {:else}
diff --git a/plugins/contact-resources/src/components/EmployeeAccountRefPresenter.svelte b/plugins/contact-resources/src/components/EmployeeAccountRefPresenter.svelte index ee14bc7478..bc0b0f6259 100644 --- a/plugins/contact-resources/src/components/EmployeeAccountRefPresenter.svelte +++ b/plugins/contact-resources/src/components/EmployeeAccountRefPresenter.svelte @@ -30,5 +30,5 @@ {#if account} - + {/if} diff --git a/plugins/contact-resources/src/index.ts b/plugins/contact-resources/src/index.ts index 5cdc43f91b..e5e4b42aab 100644 --- a/plugins/contact-resources/src/index.ts +++ b/plugins/contact-resources/src/index.ts @@ -68,6 +68,7 @@ import EmployeePresenter from './components/EmployeePresenter.svelte' import EmployeeRefPresenter from './components/EmployeeRefPresenter.svelte' import MemberPresenter from './components/MemberPresenter.svelte' import Members from './components/Members.svelte' +import MembersBox from './components/MembersBox.svelte' import MembersPresenter from './components/MembersPresenter.svelte' import MergeEmployee from './components/MergeEmployee.svelte' import OrganizationEditor from './components/OrganizationEditor.svelte' @@ -141,7 +142,8 @@ export { UserInfo, IconMembers, SelectAvatars, - UserBoxItems + UserBoxItems, + MembersBox } const toObjectSearchResult = (e: WithLookup): ObjectSearchResult => ({ @@ -311,7 +313,8 @@ export default async (): Promise => ({ EmployeeFilter, EmployeeFilterValuePresenter, EmployeeAccountFilterValuePresenter, - DeleteConfirmationPopup + DeleteConfirmationPopup, + EmployeeAccountRefPresenter }, completion: { EmployeeQuery: async ( diff --git a/plugins/lead-resources/src/components/EditFunnel.svelte b/plugins/lead-resources/src/components/EditFunnel.svelte index ce0ed75862..1df85264f1 100644 --- a/plugins/lead-resources/src/components/EditFunnel.svelte +++ b/plugins/lead-resources/src/components/EditFunnel.svelte @@ -18,7 +18,8 @@ import type { Ref } from '@hcengineering/core' import core from '@hcengineering/core' import { Panel } from '@hcengineering/panel' - import { createQuery, getClient, MembersBox } from '@hcengineering/presentation' + import { createQuery, getClient } from '@hcengineering/presentation' + import { MembersBox } from '@hcengineering/contact-resources' import type { Funnel } from '@hcengineering/lead' import { FullDescriptionBox } from '@hcengineering/text-editor' import { EditBox, Grid } from '@hcengineering/ui' diff --git a/plugins/lead-resources/src/components/MyLeads.svelte b/plugins/lead-resources/src/components/MyLeads.svelte index 483eb4ac83..66875e8b86 100644 --- a/plugins/lead-resources/src/components/MyLeads.svelte +++ b/plugins/lead-resources/src/components/MyLeads.svelte @@ -49,7 +49,7 @@ $: queries = { assigned, created, subscribed } $: mode = $resolvedLocationStore.query?.mode ?? undefined - let searchQuery: DocumentQuery = { ...baseQuery } + let searchQuery: DocumentQuery = { ...(baseQuery ?? {}) } function updateSearchQuery (search: string): void { searchQuery = search === '' ? { ...baseQuery } : { ...baseQuery, $search: search } } @@ -72,11 +72,11 @@ ) } $: if (mode === 'subscribed') getSubscribed() - $: if (mode === undefined || queries[mode] === undefined) { + $: if (mode === undefined || (queries as any)[mode] === undefined) { ;[[mode]] = config } $: if (mode !== undefined) { - baseQuery = { ...queries[mode] } + baseQuery = { ...((queries as any)[mode] ?? {}) } modeSelectorProps = { config, mode, @@ -101,6 +101,9 @@ .findOne(view.class.Viewlet, { attachTo: _class, descriptor: task.viewlet.StatusTable }) .then((res) => { viewlet = res + if (res == null) { + return + } preferenceQuery.query( view.class.ViewletPreference, { diff --git a/plugins/login-assets/lang/en.json b/plugins/login-assets/lang/en.json index 97cec6020f..46d2947609 100644 --- a/plugins/login-assets/lang/en.json +++ b/plugins/login-assets/lang/en.json @@ -27,7 +27,7 @@ "WantAnotherWorkspace": "Want to create another workspace?", "ChangeAccount": "Change account", "NotSeeingWorkspace": "Not seeing your workspace?", - "WorkspaceNameRule": "The workspace name can contain lowercase letters, numbers and symbol -", + "WorkspaceNameRule": "The workspace name can contain lowercase letters, numbers and symbol - (inside name)", "ForgotPassword": "Forgot your password?", "KnowPassword": "Know your password?", "Recover": "Recover", diff --git a/plugins/login-assets/lang/ru.json b/plugins/login-assets/lang/ru.json index 3de88d05f6..ae22404d5a 100644 --- a/plugins/login-assets/lang/ru.json +++ b/plugins/login-assets/lang/ru.json @@ -27,7 +27,7 @@ "WantAnotherWorkspace": "Хотите создать другое рабочее пространство?", "ChangeAccount": "Сменить пользователя", "NotSeeingWorkspace": "Не видите ваше рабочее пространство?", - "WorkspaceNameRule": "Название рабочего пространства должно состояить из строчных латинских букв, цифр и символа -", + "WorkspaceNameRule": "Название рабочего пространства должно состояить из строчных латинских букв, цифр и символа - (внутри имени)", "ForgotPassword": "Забыли пароль?", "KnowPassword": "Знаете пароль?", "Recover": "Восстановить", diff --git a/plugins/login-resources/src/components/CreateWorkspaceForm.svelte b/plugins/login-resources/src/components/CreateWorkspaceForm.svelte index 40fb22e35b..15e6a93e32 100644 --- a/plugins/login-resources/src/components/CreateWorkspaceForm.svelte +++ b/plugins/login-resources/src/components/CreateWorkspaceForm.svelte @@ -28,7 +28,7 @@ { name: 'workspace', i18n: login.string.Workspace, - rule: /^[0-9a-z\-)(]{3,63}$/, + rule: /^[0-9a-z][0-9a-z-]{2,62}[0-9a-z]$/, ruleDescr: login.string.WorkspaceNameRule } ] diff --git a/plugins/request-resources/src/components/RequestActions.svelte b/plugins/request-resources/src/components/RequestActions.svelte index 8582d4387e..e3e9c9603e 100644 --- a/plugins/request-resources/src/components/RequestActions.svelte +++ b/plugins/request-resources/src/components/RequestActions.svelte @@ -15,7 +15,7 @@ getParentActions()} shortDropbox={model.specials !== undefined} > - {#each spaces as space, i (space._id)} + {#each filteredSpaces as space, i (space._id)} {#await getPresenter(space._class) then presenter} {#if separate && model.specials && i !== 0}{/if} {#if model.specials && presenter} diff --git a/plugins/workbench-resources/src/index.ts b/plugins/workbench-resources/src/index.ts index d25bdc2262..3c84fbe1ec 100644 --- a/plugins/workbench-resources/src/index.ts +++ b/plugins/workbench-resources/src/index.ts @@ -13,7 +13,7 @@ // limitations under the License. // -import { Space } from '@hcengineering/core' +import { AccountRole, Space, getCurrentAccount } from '@hcengineering/core' import { Resources } from '@hcengineering/platform' import ApplicationPresenter from './components/ApplicationPresenter.svelte' import Archive from './components/Archive.svelte' @@ -42,7 +42,8 @@ export default async (): Promise => ({ Workbench }, function: { - HasArchiveSpaces: hasArchiveSpaces + HasArchiveSpaces: hasArchiveSpaces, + IsOwner: async (docs: Space[]) => getCurrentAccount().role === AccountRole.Owner }, actionImpl: { Navigate: doNavigate diff --git a/plugins/workbench/src/index.ts b/plugins/workbench/src/index.ts index 635a179a69..d17f50afbf 100644 --- a/plugins/workbench/src/index.ts +++ b/plugins/workbench/src/index.ts @@ -59,6 +59,8 @@ export interface SpacesNavModel { // Child special items. specials?: SpecialNavModel[] + + visibleIf?: Resource<(space: Space) => Promise> } /** diff --git a/pods/account/src/index.ts b/pods/account/src/index.ts index aef53ee203..7e5e732a01 100644 --- a/pods/account/src/index.ts +++ b/pods/account/src/index.ts @@ -97,7 +97,6 @@ export function serveAccount (methods: Record, productId } const db = client.db(ACCOUNT_DB) const result = await method(db, productId, request, token) - console.log(result) ctx.body = result }) diff --git a/server/account/src/index.ts b/server/account/src/index.ts index 31d6ed4629..3267184171 100644 --- a/server/account/src/index.ts +++ b/server/account/src/index.ts @@ -104,6 +104,7 @@ export interface Account { // Defined for server admins only admin?: boolean confirmed?: boolean + lastWorkspace?: number } /** @@ -115,6 +116,7 @@ export interface Workspace { organisation: string accounts: ObjectId[] productId: string + disabled?: boolean } /** @@ -285,6 +287,9 @@ export async function selectWorkspace ( const workspaceInfo = await getWorkspace(db, productId, workspace) if (workspaceInfo !== null) { + if (workspaceInfo.disabled === true) { + throw new PlatformError(new Status(Severity.ERROR, platform.status.WorkspaceNotFound, { workspace })) + } const workspaces = accountInfo.workspaces for (const w of workspaces) { @@ -347,6 +352,7 @@ export async function join ( ): Promise { const invite = await getInvite(db, inviteId) const workspace = await checkInvite(invite, email) + console.log(`join attempt:${email}, ${workspace.name}`) await assignWorkspace(db, productId, email, workspace.name) const token = (await login(db, productId, email, password)).token @@ -360,6 +366,7 @@ export async function join ( */ export async function confirmEmail (db: Db, email: string): Promise { const account = await getAccount(db, email) + console.log(`confirm email:${email}`) if (account === null) { throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotFound, { account: accountId })) @@ -446,6 +453,7 @@ export async function signUpJoin ( last: string, inviteId: ObjectId ): Promise { + console.log(`signup join:${email} ${first} ${last}`) const invite = await getInvite(db, inviteId) const workspace = await checkInvite(invite, email) await createAcc(db, productId, email, password, first, last, invite?.emailMask === email) @@ -529,6 +537,7 @@ export async function createAccount ( export async function listWorkspaces (db: Db, productId: string): Promise { return (await db.collection(WORKSPACE_COLLECTION).find(withProductId(productId, {})).toArray()) .map((it) => ({ ...it, productId })) + .filter((it) => it.disabled !== true) .map(trimWorkspace) } @@ -612,20 +621,59 @@ export async function upgradeWorkspace ( export const createUserWorkspace = (version: Data, txes: Tx[], migrationOperation: [string, MigrateOperation][]) => async (db: Db, productId: string, token: string, workspace: string): Promise => { + if (!/^[0-9a-z][0-9a-z-]{2,62}[0-9a-z]$/.test(workspace)) { + throw new PlatformError(new Status(Severity.ERROR, platform.status.InvalidId, { id: workspace })) + } + const { email, extra } = decodeToken(token) - if (extra?.confirmed === false) { + const nonConfirmed = extra?.confirmed === false + console.log(`Creating workspace ${workspace} for ${email} ${nonConfirmed ? 'non confirmed' : 'confirmed'}`) + + if (nonConfirmed) { + throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotFound, { account: email })) + } + const info = await getAccount(db, email) + if (info === null) { throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotFound, { account: email })) } - await createWorkspace(version, txes, migrationOperation, db, productId, workspace, '') + + if (info.lastWorkspace !== undefined) { + if (Date.now() - info.lastWorkspace < 60 * 1000) { + throw new PlatformError(new Status(Severity.ERROR, platform.status.WorkspaceRateLimit, { workspace })) + } + } + + try { + await createWorkspace(version, txes, migrationOperation, db, productId, workspace, '') + } catch (err: any) { + console.error(err) + // We need to drop workspace, to prevent wrong data usage. + const ws = await getWorkspace(db, productId, workspace) + if (ws === null) { + throw new PlatformError(new Status(Severity.ERROR, platform.status.WorkspaceNotFound, { workspace })) + } + await db.collection(WORKSPACE_COLLECTION).updateOne( + { + _id: ws._id + }, + { $set: { disabled: true } } + ) + throw err + } + info.lastWorkspace = Date.now() + + // Update last workspace time. + await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: info._id }, { $set: { lastWorkspace: Date.now() } }) + await assignWorkspace(db, productId, email, workspace) await setRole(email, workspace, productId, AccountRole.Owner) - const info = await getAccount(db, email) const result = { endpoint: getEndpoint(), email, token: generateToken(email, getWorkspaceId(workspace, productId), getExtra(info)), productId } + console.log(`Creating workspace ${workspace} Done`) return result } @@ -678,7 +726,9 @@ export async function getUserWorkspaces (db: Db, productId: string, token: strin .collection(WORKSPACE_COLLECTION) .find(withProductId(productId, account.admin === true ? {} : { _id: { $in: account.workspaces } })) .toArray() - ).map(trimWorkspace) + ) + .filter((it) => it.disabled !== true) + .map(trimWorkspace) } async function getWorkspaceAndAccount ( diff --git a/server/minio/src/index.ts b/server/minio/src/index.ts index f07e822d85..4e0f67eb93 100644 --- a/server/minio/src/index.ts +++ b/server/minio/src/index.ts @@ -57,17 +57,24 @@ export class MinioService { } async list (workspaceId: WorkspaceId, prefix?: string): Promise { - const items = new Map() - const list = await this.client.listObjects(getBucketId(workspaceId), prefix, true) - await new Promise((resolve) => { - list.on('data', (data) => { - items.set(data.name, { metaData: {}, ...data }) - }) - list.on('end', () => { - resolve(null) + try { + const items = new Map() + const list = await this.client.listObjects(getBucketId(workspaceId), prefix, true) + await new Promise((resolve) => { + list.on('data', (data) => { + items.set(data.name, { metaData: {}, ...data }) + }) + list.on('end', () => { + resolve(null) + }) }) - }) - return Array.from(items.values()) + return Array.from(items.values()) + } catch (err: any) { + if (((err?.message as string) ?? '').includes('Invalid bucket name')) { + return [] + } + throw err + } } async stat (workspaceId: WorkspaceId, objectName: string): Promise {