mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-31 12:19:47 +02:00
UBERF-9732: Use huly id as primary social id (#8499)
* uberf-9732: use huly id as primary social id Signed-off-by: Alexey Zinoviev <alexey.zinoviev@xored.com> * uberf-9732: fix person id filter Signed-off-by: Alexey Zinoviev <alexey.zinoviev@xored.com>
This commit is contained in:
Vendored
+3
-2
@@ -53,13 +53,14 @@
|
||||
"MODEL_JSON": "${workspaceRoot}/models/all/bundle/model.json",
|
||||
// "SERVER_PROVIDER":"uweb"
|
||||
"SERVER_PROVIDER": "ws",
|
||||
"MODEL_VERSION": "0.7.1",
|
||||
"MODEL_VERSION": "0.7.48",
|
||||
// "VERSION": "0.6.289",
|
||||
"ELASTIC_INDEX_NAME": "local_storage_index",
|
||||
"UPLOAD_URL": "/files",
|
||||
"AI_BOT_URL": "http://localhost:4010",
|
||||
"STATS_URL": "http://huly.local:4900",
|
||||
"STREAM_URL": "http://huly.local:1080/recording"
|
||||
"STREAM_URL": "http://huly.local:1080/recording",
|
||||
"QUEUE_CONFIG": "localhost:19092"
|
||||
},
|
||||
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
|
||||
"runtimeVersion": "20",
|
||||
|
||||
@@ -998,10 +998,6 @@ export function createModel (builder: Builder): void {
|
||||
group: 'bottom'
|
||||
})
|
||||
|
||||
builder.mixin(core.class.TypePersonId, core.class.Class, view.mixin.AttributeFilter, {
|
||||
component: view.component.ValueFilter
|
||||
})
|
||||
|
||||
builder.mixin(core.class.TypeAccountUuid, core.class.Class, view.mixin.AttributeFilter, {
|
||||
component: view.component.ValueFilter
|
||||
})
|
||||
@@ -1242,10 +1238,6 @@ export function createModel (builder: Builder): void {
|
||||
presenter: view.component.StringFilterPresenter
|
||||
})
|
||||
|
||||
builder.mixin(core.class.TypePersonId, core.class.Class, view.mixin.AttributeFilterPresenter, {
|
||||
presenter: view.component.StringFilterPresenter
|
||||
})
|
||||
|
||||
builder.mixin(core.class.TypeAccountUuid, core.class.Class, view.mixin.AttributeFilterPresenter, {
|
||||
presenter: view.component.StringFilterPresenter
|
||||
})
|
||||
@@ -1304,6 +1296,10 @@ export function createModel (builder: Builder): void {
|
||||
presenter: view.component.PersonIdFilterValuePresenter
|
||||
})
|
||||
|
||||
builder.mixin(core.class.TypePersonId, core.class.Class, view.mixin.AttributeFilter, {
|
||||
component: view.component.PersonIdFilter
|
||||
})
|
||||
|
||||
builder.mixin(core.class.TypeAccountUuid, core.class.Class, view.mixin.AttributePresenter, {
|
||||
presenter: view.component.PersonIdPresenter,
|
||||
arrayPresenter: view.component.PersonArrayEditor
|
||||
|
||||
@@ -29,7 +29,8 @@ import {
|
||||
type WorkspaceUserOperation,
|
||||
type WorkspaceUuid,
|
||||
type PersonId,
|
||||
type SocialIdType
|
||||
type SocialIdType,
|
||||
type AccountUuid
|
||||
} from '@hcengineering/core'
|
||||
import platform, { PlatformError, Severity, Status } from '@hcengineering/platform'
|
||||
import type {
|
||||
@@ -76,7 +77,7 @@ export interface AccountClient {
|
||||
navigateUrl?: string,
|
||||
expHours?: number
|
||||
) => Promise<string>
|
||||
leaveWorkspace: (account: string) => Promise<LoginInfo | null>
|
||||
leaveWorkspace: (account: AccountUuid) => Promise<LoginInfo | null>
|
||||
changeUsername: (first: string, last: string) => Promise<void>
|
||||
changePassword: (oldPassword: string, newPassword: string) => Promise<void>
|
||||
signUpJoin: (
|
||||
@@ -152,7 +153,7 @@ export interface AccountClient {
|
||||
deleteIntegrationSecret: (integrationSecretKey: IntegrationSecretKey) => Promise<void>
|
||||
getIntegrationSecret: (integrationSecretKey: IntegrationSecretKey) => Promise<IntegrationSecret | null>
|
||||
listIntegrationsSecrets: (filter: Partial<IntegrationSecretKey>) => Promise<IntegrationSecret[]>
|
||||
getAccountInfo: (uuid: PersonUuid) => Promise<AccountInfo>
|
||||
getAccountInfo: (uuid: AccountUuid) => Promise<AccountInfo>
|
||||
|
||||
setCookie: () => Promise<void>
|
||||
deleteCookie: () => Promise<void>
|
||||
@@ -355,7 +356,7 @@ class AccountClientImpl implements AccountClient {
|
||||
return await this.rpc(request)
|
||||
}
|
||||
|
||||
async leaveWorkspace (account: string): Promise<LoginInfo | null> {
|
||||
async leaveWorkspace (account: AccountUuid): Promise<LoginInfo | null> {
|
||||
const request = {
|
||||
method: 'leaveWorkspace' as const,
|
||||
params: { account }
|
||||
@@ -831,7 +832,7 @@ class AccountClientImpl implements AccountClient {
|
||||
return await this.rpc(request)
|
||||
}
|
||||
|
||||
async getAccountInfo (uuid: PersonUuid): Promise<AccountInfo> {
|
||||
async getAccountInfo (uuid: AccountUuid): Promise<AccountInfo> {
|
||||
const request = {
|
||||
method: 'getAccountInfo' as const,
|
||||
params: { accountId: uuid }
|
||||
|
||||
@@ -544,7 +544,7 @@ export interface Person {
|
||||
}
|
||||
|
||||
export interface PersonInfo extends BasePerson {
|
||||
socialIds: PersonId[]
|
||||
socialIds: SocialId[]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -830,7 +830,7 @@ export interface WorkspaceInfoWithStatus extends WorkspaceInfo {
|
||||
}
|
||||
|
||||
export interface WorkspaceMemberInfo {
|
||||
person: PersonUuid
|
||||
person: AccountUuid
|
||||
role: AccountRole
|
||||
}
|
||||
|
||||
|
||||
@@ -930,8 +930,9 @@ export function pickPrimarySocialId (socialIds: SocialId[]): SocialId {
|
||||
if (socialIds.length === 0) {
|
||||
throw new Error('No social ids provided')
|
||||
}
|
||||
const hulySocialIds = socialIds.filter((si) => si.type === SocialIdType.HULY)
|
||||
|
||||
return socialIds[0]
|
||||
return hulySocialIds[0] ?? socialIds[0]
|
||||
}
|
||||
|
||||
export function notEmpty<T> (id: T | undefined | null): id is T {
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
<!--
|
||||
// Copyright © 2023 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 contact, { Person, SocialIdentity } from '@hcengineering/contact'
|
||||
import core, { FindResult, getObjectValue, includesAny, PersonId, Ref, Space, WithLookup } from '@hcengineering/core'
|
||||
import presentation, { getClient } from '@hcengineering/presentation'
|
||||
import ui, {
|
||||
deviceOptionsStore,
|
||||
EditWithIcon,
|
||||
Icon,
|
||||
IconCheck,
|
||||
IconSearch,
|
||||
Loading,
|
||||
resizeObserver
|
||||
} from '@hcengineering/ui'
|
||||
import view, { Filter } from '@hcengineering/view'
|
||||
import { FILTER_DEBOUNCE_MS, sortFilterValues } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import PersonPresenter from './PersonPresenter.svelte'
|
||||
|
||||
export let filter: Filter
|
||||
export let space: Ref<Space> | undefined = undefined
|
||||
export let onChange: (e: Filter) => void
|
||||
|
||||
const client = getClient()
|
||||
filter.modes = filter.modes === undefined ? [view.filter.FilterObjectIn, view.filter.FilterObjectNin] : filter.modes
|
||||
filter.mode = filter.mode === undefined ? filter.modes[0] : filter.mode
|
||||
|
||||
let socialIdentities: (WithLookup<SocialIdentity> | undefined | null)[] = []
|
||||
let socialIdentitiesPromise: Promise<FindResult<SocialIdentity>> | undefined
|
||||
let personIdToPersonMap: Record<PersonId, Ref<Person>> = {}
|
||||
let personToPersonIdsMap: Record<Ref<Person>, PersonId[]> = {}
|
||||
let persons: Ref<Person>[] = []
|
||||
const targets = new Set<any>()
|
||||
|
||||
let filterUpdateTimeout: any | undefined
|
||||
let search: string = ''
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function getValues (search: string): Promise<void> {
|
||||
if (socialIdentitiesPromise !== undefined) {
|
||||
await socialIdentitiesPromise
|
||||
}
|
||||
targets.clear()
|
||||
|
||||
const spaces = (
|
||||
await client.findAll(core.class.Space, { archived: true }, { projection: { _id: 1, archived: 1, _class: 1 } })
|
||||
).map((it) => it._id)
|
||||
|
||||
const baseObjects = await client.findAll(
|
||||
filter.key._class,
|
||||
space !== undefined ? { space } : { space: { $nin: spaces } },
|
||||
{
|
||||
projection: { [filter.key.key]: 1, space: 1 }
|
||||
}
|
||||
)
|
||||
for (const object of baseObjects) {
|
||||
const socialIdentity = getObjectValue(filter.key.key, object) ?? undefined
|
||||
targets.add(socialIdentity)
|
||||
}
|
||||
for (const object of filter.value) {
|
||||
targets.add(object)
|
||||
}
|
||||
|
||||
const resultQuery =
|
||||
search !== ''
|
||||
? {
|
||||
'$lookup.attachedTo.name': { $like: '%' + search + '%' },
|
||||
_id: { $in: Array.from(targets.keys()) }
|
||||
}
|
||||
: {
|
||||
_id: { $in: Array.from(targets.keys()) }
|
||||
}
|
||||
socialIdentitiesPromise = client.findAll(contact.class.SocialIdentity, resultQuery, {
|
||||
lookup: { attachedTo: contact.class.Person }
|
||||
})
|
||||
socialIdentities = await socialIdentitiesPromise
|
||||
if (targets.has(undefined)) {
|
||||
socialIdentities.unshift(undefined)
|
||||
}
|
||||
|
||||
personIdToPersonMap = (socialIdentities ?? []).reduce<Record<string, Ref<Person>>>((acc, sid) => {
|
||||
if (sid == null) return acc
|
||||
|
||||
const person = sid?.$lookup?.attachedTo
|
||||
|
||||
if (person == null) return acc
|
||||
|
||||
acc[sid._id] = person._id
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
personToPersonIdsMap = (socialIdentities ?? []).reduce<Record<Ref<Person>, PersonId[]>>((acc, sid) => {
|
||||
if (sid == null) return acc
|
||||
|
||||
const person = sid?.$lookup?.attachedTo
|
||||
|
||||
if (person == null) return acc
|
||||
|
||||
if (acc[person._id] == null) {
|
||||
acc[person._id] = []
|
||||
}
|
||||
|
||||
acc[person._id].push(sid._id)
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
persons = sortFilterValues(Array.from(new Set(Object.values(personIdToPersonMap))), (p) =>
|
||||
isPersonSelected(p, filter.value)
|
||||
)
|
||||
|
||||
socialIdentitiesPromise = undefined
|
||||
}
|
||||
|
||||
function isPersonSelected (person: Ref<Person>, selectedIds: any[]): boolean {
|
||||
const personSocialIds = personToPersonIdsMap[person] ?? []
|
||||
|
||||
return includesAny(personSocialIds, selectedIds)
|
||||
}
|
||||
|
||||
function handleFilterToggle (person: Ref<Person>): void {
|
||||
const personSocialIds = personToPersonIdsMap[person] ?? []
|
||||
if (isPersonSelected(person, filter.value)) {
|
||||
filter.value = filter.value.filter((p) => !personSocialIds.includes(p))
|
||||
} else {
|
||||
filter.value = [...filter.value, ...personSocialIds]
|
||||
}
|
||||
|
||||
updateFilter()
|
||||
}
|
||||
|
||||
function updateFilter (): void {
|
||||
clearTimeout(filterUpdateTimeout)
|
||||
|
||||
filterUpdateTimeout = setTimeout(() => {
|
||||
onChange(filter)
|
||||
}, FILTER_DEBOUNCE_MS)
|
||||
}
|
||||
|
||||
$: {
|
||||
void getValues(search)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="selectPopup" use:resizeObserver={() => dispatch('changeContent')}>
|
||||
<div class="header">
|
||||
<EditWithIcon
|
||||
icon={IconSearch}
|
||||
size={'large'}
|
||||
width={'100%'}
|
||||
autoFocus={!$deviceOptionsStore.isMobile}
|
||||
bind:value={search}
|
||||
placeholder={presentation.string.Search}
|
||||
/>
|
||||
</div>
|
||||
<div class="scroll">
|
||||
<div class="box">
|
||||
{#if socialIdentitiesPromise}
|
||||
<Loading />
|
||||
{:else}
|
||||
{#each persons as person}
|
||||
<button
|
||||
class="menu-item no-focus flex-row-center"
|
||||
on:click={() => {
|
||||
handleFilterToggle(person)
|
||||
}}
|
||||
>
|
||||
<div class="clear-mins flex-grow">
|
||||
<PersonPresenter
|
||||
value={person}
|
||||
shouldShowPlaceholder
|
||||
defaultName={ui.string.NotSelected}
|
||||
disabled
|
||||
noUnderline
|
||||
/>
|
||||
</div>
|
||||
<div class="check pointer-events-none">
|
||||
{#if isPersonSelected(person, filter.value)}
|
||||
<Icon icon={IconCheck} size={'small'} />
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-space" />
|
||||
</div>
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
import {
|
||||
AccountRole,
|
||||
SocialIdType,
|
||||
type AccountUuid,
|
||||
type Class,
|
||||
type Client,
|
||||
type Data,
|
||||
@@ -102,6 +103,7 @@ import PersonFilterValuePresenter from './components/PersonFilterValuePresenter.
|
||||
import PersonEditor from './components/PersonEditor.svelte'
|
||||
import PersonIcon from './components/PersonIcon.svelte'
|
||||
import PersonPresenter from './components/PersonPresenter.svelte'
|
||||
import PersonIdFilter from './components/PersonIdFilter.svelte'
|
||||
import PersonRefPresenter from './components/PersonRefPresenter.svelte'
|
||||
import SelectAvatars from './components/SelectAvatars.svelte'
|
||||
import SelectUsersPopup from './components/SelectUsersPopup.svelte'
|
||||
@@ -199,7 +201,8 @@ export {
|
||||
UserDetails,
|
||||
UserInfo,
|
||||
UsersList,
|
||||
UsersPopup
|
||||
UsersPopup,
|
||||
PersonIdFilter
|
||||
}
|
||||
|
||||
const toObjectSearchResult = (e: WithLookup<Contact>): ObjectSearchResult => ({
|
||||
@@ -299,7 +302,7 @@ async function kickEmployee (doc: Person): Promise<void> {
|
||||
|
||||
if (doc.personUuid != null) {
|
||||
const leaveWorkspace = await getResource(login.function.LeaveWorkspace)
|
||||
await leaveWorkspace(doc.personUuid)
|
||||
await leaveWorkspace(doc.personUuid as AccountUuid)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -390,7 +393,8 @@ export default async (): Promise<Resources> => ({
|
||||
EditOrganizationPanel,
|
||||
ChannelIcon,
|
||||
SpaceMembersEditor,
|
||||
ContactNamePresenter
|
||||
ContactNamePresenter,
|
||||
PersonIdFilter
|
||||
},
|
||||
completion: {
|
||||
EmployeeQuery: async (
|
||||
|
||||
@@ -238,7 +238,8 @@ export const contactPlugin = plugin(contactId, {
|
||||
CreateGuest: '' as AnyComponent,
|
||||
SpaceMembersEditor: '' as AnyComponent,
|
||||
ContactNamePresenter: '' as AnyComponent,
|
||||
PersonFilterValuePresenter: '' as AnyComponent
|
||||
PersonFilterValuePresenter: '' as AnyComponent,
|
||||
PersonIdFilter: '' as AnyComponent
|
||||
},
|
||||
channelProvider: {
|
||||
Email: '' as Ref<ChannelProvider>,
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
MeasureContext,
|
||||
notEmpty,
|
||||
PersonId,
|
||||
pickPrimarySocialId,
|
||||
Ref,
|
||||
SocialId,
|
||||
toIdMap,
|
||||
@@ -283,15 +284,6 @@ export function formatContactName (
|
||||
return name
|
||||
}
|
||||
|
||||
// TODO: remove me in favor of the same util in core package
|
||||
export function pickPrimarySocialId (ids: PersonId[]): PersonId {
|
||||
if (ids.length === 0) {
|
||||
throw new Error('No social ids provided')
|
||||
}
|
||||
|
||||
return ids[0]
|
||||
}
|
||||
|
||||
export function includesAny (members: PersonId[], ids: PersonId[]): boolean {
|
||||
return members.some((m) => ids.includes(m))
|
||||
}
|
||||
@@ -337,13 +329,13 @@ export async function getPersonRefsBySocialIds (
|
||||
}
|
||||
|
||||
export async function getPrimarySocialId (client: Client, person: Ref<Person>): Promise<PersonId | undefined> {
|
||||
const socialIds = await client.findAll(contact.class.SocialIdentity, { attachedTo: person })
|
||||
const socialIds = await client.findAll(contact.class.SocialIdentity, { attachedTo: person, verifiedOn: { $gt: 0 } })
|
||||
|
||||
if (socialIds.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
return pickPrimarySocialId(socialIds.map((it) => it._id))
|
||||
return pickPrimarySocialId(socialIds)._id
|
||||
}
|
||||
|
||||
export async function getAllSocialStringsByPersonId (client: Client, personId: PersonId): Promise<PersonId[]> {
|
||||
@@ -393,12 +385,6 @@ export async function getAllAccounts (client: Client): Promise<AccountUuid[]> {
|
||||
return employees.map((it) => it.personUuid).filter(notEmpty)
|
||||
}
|
||||
|
||||
export async function getAllEmployeesPrimarySocialStrings (client: Client): Promise<PersonId[]> {
|
||||
const socialStringsByPerson = getSocialStringsByEmployee(client)
|
||||
|
||||
return Object.values(socialStringsByPerson).map((it) => pickPrimarySocialId(it))
|
||||
}
|
||||
|
||||
export async function getAllUserAccounts (client: Client): Promise<AccountUuid[]> {
|
||||
const employees = await client.findAll(contact.mixin.Employee, { active: true })
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
AccountRole,
|
||||
concatLink,
|
||||
parseSocialIdString,
|
||||
type AccountUuid,
|
||||
type Person,
|
||||
type WorkspaceInfoWithStatus,
|
||||
type WorkspaceUserOperation
|
||||
@@ -661,7 +662,7 @@ export async function changeUsername (first: string, last: string): Promise<void
|
||||
}
|
||||
}
|
||||
|
||||
export async function leaveWorkspace (account: string): Promise<LoginInfo | null> {
|
||||
export async function leaveWorkspace (account: AccountUuid): Promise<LoginInfo | null> {
|
||||
return await getAccountClient().leaveWorkspace(account)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<!--
|
||||
// Copyright © 2023 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 { Ref, Space } from '@hcengineering/core'
|
||||
import { Component } from '@hcengineering/ui'
|
||||
import contact from '@hcengineering/contact'
|
||||
import { Filter } from '@hcengineering/view'
|
||||
|
||||
export let filter: Filter
|
||||
export let space: Ref<Space> | undefined = undefined
|
||||
export let onChange: (e: Filter) => void
|
||||
</script>
|
||||
|
||||
<Component is={contact.component.PersonIdFilter} props={{ filter, space, onChange }} />
|
||||
@@ -18,17 +18,20 @@
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import contact, { getPersonRefsBySocialIds, Person } from '@hcengineering/contact'
|
||||
|
||||
export let value: [any, PersonId][]
|
||||
export let value: PersonId[]
|
||||
|
||||
const client = getClient()
|
||||
let persons: Ref<Person>[] = []
|
||||
$: void getPersons(value.map((p) => p[1]).flat())
|
||||
$: void getPersons(value)
|
||||
|
||||
async function getPersons (ids: PersonId[]): Promise<void> {
|
||||
if (ids !== undefined) {
|
||||
const personsMap = await getPersonRefsBySocialIds(client, ids)
|
||||
persons = Object.values(personsMap)
|
||||
console.log(persons)
|
||||
if (ids.length === 0) {
|
||||
persons = []
|
||||
} else {
|
||||
const personsMap = await getPersonRefsBySocialIds(client, ids)
|
||||
persons = Object.values(personsMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -98,6 +98,7 @@ import StatusPresenter from './components/status/StatusPresenter.svelte'
|
||||
import StatusRefPresenter from './components/status/StatusRefPresenter.svelte'
|
||||
import PersonArrayEditor from './components/PersonArrayEditor.svelte'
|
||||
import PersonIdPresenter from './components/PersonIdPresenter.svelte'
|
||||
import PersonIdFilter from './components/filter/PersonIdFilter.svelte'
|
||||
import PersonIdFilterValuePresenter from './components/filter/PersonIdFilterValuePresenter.svelte'
|
||||
import AudioViewer from './components/viewer/AudioViewer.svelte'
|
||||
import ImageViewer from './components/viewer/ImageViewer.svelte'
|
||||
@@ -176,6 +177,7 @@ export { default as NavLink } from './components/navigator/NavLink.svelte'
|
||||
export { default as StatusPresenter } from './components/status/StatusPresenter.svelte'
|
||||
export { default as StatusRefPresenter } from './components/status/StatusRefPresenter.svelte'
|
||||
export { default as PersonIdPresenter } from './components/PersonIdPresenter.svelte'
|
||||
export { default as PersonIdFilter } from './components/filter/PersonIdFilter.svelte'
|
||||
export { default as PersonIdFilterValuePresenter } from './components/filter/PersonIdFilterValuePresenter.svelte'
|
||||
export { default as FoldersBrowser } from './components/folders/FoldersBrowser.svelte'
|
||||
export { default as RelationsEditor } from './components/RelationsEditor.svelte'
|
||||
@@ -309,6 +311,7 @@ export default async (): Promise<Resources> => ({
|
||||
StatusRefPresenter,
|
||||
PersonArrayEditor,
|
||||
PersonIdPresenter,
|
||||
PersonIdFilter,
|
||||
PersonIdFilterValuePresenter,
|
||||
DateFilterPresenter,
|
||||
StringFilterPresenter,
|
||||
|
||||
@@ -175,6 +175,7 @@ const view = plugin(viewId, {
|
||||
SearchSelector: '' as AnyComponent,
|
||||
FoldersBrowser: '' as AnyComponent,
|
||||
PersonIdPresenter: '' as AnyComponent,
|
||||
PersonIdFilter: '' as AnyComponent,
|
||||
RolePresenter: '' as AnyComponent
|
||||
},
|
||||
ids: {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
import calendar, { Calendar, Event, ExternalCalendar } from '@hcengineering/calendar'
|
||||
import contactPlugin, { Employee, Person, SocialIdentity, pickPrimarySocialId } from '@hcengineering/contact'
|
||||
import contactPlugin, { Employee, Person, SocialIdentity } from '@hcengineering/contact'
|
||||
import core, {
|
||||
Class,
|
||||
concatLink,
|
||||
@@ -33,12 +33,13 @@ import core, {
|
||||
TxProcessor,
|
||||
TxRemoveDoc,
|
||||
TxUpdateDoc,
|
||||
AccountUuid
|
||||
AccountUuid,
|
||||
pickPrimarySocialId
|
||||
} from '@hcengineering/core'
|
||||
import serverCalendar from '@hcengineering/server-calendar'
|
||||
import { getMetadata, getResource } from '@hcengineering/platform'
|
||||
import { TriggerControl } from '@hcengineering/server-core'
|
||||
import { getPerson, getSocialStrings } from '@hcengineering/server-contact'
|
||||
import { getPerson, getSocialStrings, getSocialIds } from '@hcengineering/server-contact'
|
||||
import { getHTMLPresenter, getTextPresenter } from '@hcengineering/server-notification-resources'
|
||||
import { generateToken } from '@hcengineering/server-token'
|
||||
|
||||
@@ -95,10 +96,10 @@ export async function OnEmployee (txes: Tx[], control: TriggerControl): Promise<
|
||||
if (ctx.attributes?.active !== true) continue
|
||||
if (await checkCalendarsExist(control, ctx.objectId)) continue
|
||||
|
||||
const socialIds = await getSocialStrings(control, ctx.objectId)
|
||||
const socialIds = await getSocialIds(control, ctx.objectId)
|
||||
if (socialIds.length === 0) continue
|
||||
|
||||
const socialId = pickPrimarySocialId(socialIds)
|
||||
const socialId = pickPrimarySocialId(socialIds)._id
|
||||
|
||||
const employee = (
|
||||
await control.findAll(
|
||||
@@ -283,10 +284,12 @@ async function eventForNewParticipants (
|
||||
const { _class, space, attachedTo, attachedToClass, collection, ...attr } = event
|
||||
const data = attr as any as Data<Event>
|
||||
for (const part of newParticipants) {
|
||||
const socialStrings = await getSocialStrings(control, part)
|
||||
if (socialStrings.length === 0) continue
|
||||
const socialIds = await getSocialIds(control, part)
|
||||
if (socialIds.length === 0) continue
|
||||
const socialStrings = socialIds.map((si) => si._id)
|
||||
if (socialStrings.includes(event.createdBy ?? event.modifiedBy)) continue
|
||||
const primarySocialString = pickPrimarySocialId(socialStrings)
|
||||
|
||||
const primarySocialString = pickPrimarySocialId(socialIds)._id
|
||||
const calendar = getCalendar(calendars, socialStrings)
|
||||
if (calendar === undefined) continue
|
||||
const innerTx = control.txFactory.createTxCreateDoc(
|
||||
@@ -355,10 +358,11 @@ async function onEventCreate (ctx: TxCreateDoc<Event>, control: TriggerControl):
|
||||
const calendars = await control.findAll(control.ctx, calendar.class.Calendar, { hidden: false })
|
||||
const access = 'reader'
|
||||
for (const part of event.participants) {
|
||||
const socialStrings = await getSocialStrings(control, part as Ref<Person>)
|
||||
if (socialStrings.length === 0) continue
|
||||
const socialIds = await getSocialIds(control, part as Ref<Person>)
|
||||
if (socialIds.length === 0) continue
|
||||
const socialStrings = socialIds.map((si) => si._id)
|
||||
if (socialStrings.includes(event.createdBy ?? event.modifiedBy)) continue
|
||||
const primarySocialString = pickPrimarySocialId(socialStrings)
|
||||
const primarySocialString = pickPrimarySocialId(socialIds)._id
|
||||
const calendar = getCalendar(calendars, socialStrings)
|
||||
if (calendar === undefined) continue
|
||||
const innerTx = control.txFactory.createTxCreateDoc(
|
||||
|
||||
@@ -14,14 +14,8 @@
|
||||
//
|
||||
|
||||
import { TriggerControl } from '@hcengineering/server-core'
|
||||
import contact, {
|
||||
Employee,
|
||||
type Person,
|
||||
PersonSpace,
|
||||
pickPrimarySocialId,
|
||||
SocialIdentityRef
|
||||
} from '@hcengineering/contact'
|
||||
import { AccountUuid, parseSocialIdString, PersonId, type Ref, toIdMap } from '@hcengineering/core'
|
||||
import contact, { Employee, type Person, PersonSpace, SocialIdentityRef } from '@hcengineering/contact'
|
||||
import { AccountUuid, parseSocialIdString, PersonId, type Ref, SocialId, toIdMap } from '@hcengineering/core'
|
||||
|
||||
export async function getCurrentPerson (control: TriggerControl): Promise<Person | undefined> {
|
||||
const { type, value } = parseSocialIdString(control.txFactory.account)
|
||||
@@ -39,13 +33,16 @@ export async function getCurrentPerson (control: TriggerControl): Promise<Person
|
||||
)[0]
|
||||
}
|
||||
|
||||
export async function getSocialStrings (control: TriggerControl, person: Ref<Person>): Promise<PersonId[]> {
|
||||
const socialIdentities = await control.findAll(control.ctx, contact.class.SocialIdentity, {
|
||||
export async function getSocialIds (control: TriggerControl, person: Ref<Person>): Promise<SocialId[]> {
|
||||
return await control.findAll(control.ctx, contact.class.SocialIdentity, {
|
||||
attachedTo: person,
|
||||
attachedToClass: contact.class.Person
|
||||
attachedToClass: contact.class.Person,
|
||||
verifiedOn: { $gt: 0 }
|
||||
})
|
||||
}
|
||||
|
||||
return socialIdentities.map((s) => s._id)
|
||||
export async function getSocialStrings (control: TriggerControl, person: Ref<Person>): Promise<PersonId[]> {
|
||||
return (await getSocialIds(control, person)).map((s) => s._id)
|
||||
}
|
||||
|
||||
export async function getSocialStringsByPersons (
|
||||
@@ -194,19 +191,6 @@ export async function getSocialIdsByAccounts (
|
||||
}, {})
|
||||
}
|
||||
|
||||
export async function getPrimarySocialIdsByAccounts (
|
||||
control: TriggerControl,
|
||||
accounts: AccountUuid[]
|
||||
): Promise<Record<AccountUuid, PersonId>> {
|
||||
return Object.entries(await getSocialIdsByAccounts(control, accounts)).reduce<Record<AccountUuid, PersonId>>(
|
||||
(acc, [account, sids]) => {
|
||||
acc[account as AccountUuid] = pickPrimarySocialId(sids)
|
||||
return acc
|
||||
},
|
||||
{}
|
||||
)
|
||||
}
|
||||
|
||||
export async function getAccountBySocialId (control: TriggerControl, socialId: PersonId): Promise<AccountUuid | null> {
|
||||
const contextAccount = control.ctx.contextData.socialStringsToUsers.get(socialId)
|
||||
if (contextAccount != null) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//
|
||||
// Copyright © 2023-2024 Hardcore Engineering Inc.
|
||||
//
|
||||
import { Person, pickPrimarySocialId, type Employee } from '@hcengineering/contact'
|
||||
import { Person, type Employee } from '@hcengineering/contact'
|
||||
import core, {
|
||||
AccountRole,
|
||||
combineAttributes,
|
||||
@@ -13,14 +13,15 @@ import core, {
|
||||
TxCreateDoc,
|
||||
TxFactory,
|
||||
TxUpdateDoc,
|
||||
systemAccountUuid,
|
||||
pickPrimarySocialId,
|
||||
type Doc,
|
||||
type RolesAssignment,
|
||||
type Timestamp,
|
||||
type TxCUD,
|
||||
systemAccountUuid
|
||||
type TxCUD
|
||||
} from '@hcengineering/core'
|
||||
import { NotificationType } from '@hcengineering/notification'
|
||||
import { getEmployees, getSocialStrings } from '@hcengineering/server-contact'
|
||||
import { getEmployees, getSocialIds } from '@hcengineering/server-contact'
|
||||
import { TriggerControl } from '@hcengineering/server-core'
|
||||
|
||||
import documents, {
|
||||
@@ -130,9 +131,14 @@ async function createDocumentTrainingRequest (doc: ControlledDocument, control:
|
||||
const dueDate: Timestamp | null =
|
||||
documentTraining.dueDays === null ? null : doc.effectiveDate + documentTraining.dueDays * 24 * 60 * 60 * 1000
|
||||
|
||||
const ownerSocialStrings = await getSocialStrings(control, doc.owner)
|
||||
const ownerSocialIds = await getSocialIds(control, doc.owner)
|
||||
if (ownerSocialIds.length === 0) {
|
||||
console.error(`Owner ${doc.owner} has no social ids`)
|
||||
return []
|
||||
}
|
||||
|
||||
// TODO: Encapsulate training request creation logic in training plugin?
|
||||
const modifiedBy = pickPrimarySocialId(ownerSocialStrings)
|
||||
const modifiedBy = pickPrimarySocialId(ownerSocialIds)._id
|
||||
|
||||
let trainees: Array<Ref<Employee>> = documentTraining.trainees
|
||||
const roles = documentTraining.roles
|
||||
|
||||
@@ -18,7 +18,8 @@ import {
|
||||
SocialIdType,
|
||||
type WorkspaceDataId,
|
||||
type WorkspaceUuid,
|
||||
type SocialKey
|
||||
type SocialKey,
|
||||
type AccountUuid
|
||||
} from '@hcengineering/core'
|
||||
import { type AccountDB, createAccount } from '@hcengineering/account'
|
||||
import { getMongoAccountDB } from './utils'
|
||||
@@ -71,9 +72,9 @@ export async function migrateFromOldAccounts (oldAccsUrl: string, accountDB: Acc
|
||||
}, 1000 * 5)
|
||||
|
||||
// Mapping between <ObjectId, UUID>
|
||||
const accountsIdToUuid: Record<string, PersonUuid> = {}
|
||||
const accountsIdToUuid: Record<string, AccountUuid> = {}
|
||||
// Mapping between <email, UUID>
|
||||
const accountsEmailToUuid: Record<string, PersonUuid> = {}
|
||||
const accountsEmailToUuid: Record<string, AccountUuid> = {}
|
||||
// Mapping between <OldId, UUID>
|
||||
const workspacesIdToUuid: Record<WorkspaceDataId, WorkspaceUuid> = {}
|
||||
|
||||
@@ -176,7 +177,7 @@ export async function migrateFromOldAccounts (oldAccsUrl: string, accountDB: Acc
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateAccount (account: OldAccount, accountDB: AccountDB): Promise<PersonUuid | undefined> {
|
||||
async function migrateAccount (account: OldAccount, accountDB: AccountDB): Promise<AccountUuid | undefined> {
|
||||
let primaryKey: SocialKey
|
||||
let secondaryKey: SocialKey | undefined
|
||||
|
||||
@@ -233,7 +234,7 @@ async function migrateAccount (account: OldAccount, accountDB: AccountDB): Promi
|
||||
|
||||
await createAccount(accountDB, personUuid, account.confirmed, false, account.createdOn)
|
||||
if (account.hash != null && account.salt != null) {
|
||||
await accountDB.account.updateOne({ uuid: personUuid }, { hash: account.hash, salt: account.salt })
|
||||
await accountDB.account.updateOne({ uuid: personUuid as AccountUuid }, { hash: account.hash, salt: account.salt })
|
||||
}
|
||||
} else {
|
||||
personUuid = existing.personUuid
|
||||
@@ -250,21 +251,22 @@ async function migrateAccount (account: OldAccount, accountDB: AccountDB): Promi
|
||||
}
|
||||
}
|
||||
|
||||
return personUuid
|
||||
return personUuid as AccountUuid
|
||||
}
|
||||
|
||||
async function migrateWorkspace (
|
||||
workspace: OldWorkspace,
|
||||
accountDB: AccountDB,
|
||||
accountsIdToUuid: Record<string, PersonUuid>,
|
||||
accountsEmailToUuid: Record<string, PersonUuid>
|
||||
accountsIdToUuid: Record<string, AccountUuid>,
|
||||
accountsEmailToUuid: Record<string, AccountUuid>
|
||||
): Promise<WorkspaceUuid | undefined> {
|
||||
if (workspace.workspaceUrl == null) {
|
||||
console.log('No workspace url, skipping', workspace.workspace)
|
||||
return
|
||||
}
|
||||
|
||||
const createdBy = workspace.createdBy !== undefined ? accountsEmailToUuid[workspace.createdBy] : ('N/A' as PersonUuid)
|
||||
const createdBy =
|
||||
workspace.createdBy !== undefined ? accountsEmailToUuid[workspace.createdBy] : ('N/A' as AccountUuid)
|
||||
if (createdBy === undefined) {
|
||||
console.log('No account found for workspace', workspace.workspace, 'created by', workspace.createdBy)
|
||||
return
|
||||
|
||||
@@ -21,7 +21,8 @@ import {
|
||||
SocialIdType,
|
||||
AccountRole,
|
||||
type Version,
|
||||
type Data
|
||||
type Data,
|
||||
type AccountUuid
|
||||
} from '@hcengineering/core'
|
||||
import {
|
||||
MongoDbCollection,
|
||||
@@ -299,7 +300,7 @@ describe('AccountMongoDbCollection', () => {
|
||||
const mockDocs = [
|
||||
{
|
||||
_id: 'id1',
|
||||
uuid: 'acc1' as PersonUuid,
|
||||
uuid: 'acc1' as AccountUuid,
|
||||
hash: { buffer: new Uint8Array([1, 2, 3]) },
|
||||
salt: { buffer: new Uint8Array([4, 5, 6]) }
|
||||
}
|
||||
@@ -319,7 +320,7 @@ describe('AccountMongoDbCollection', () => {
|
||||
|
||||
mockCollection.find.mockReturnValue(mockCursor)
|
||||
|
||||
const results = await collection.find({ uuid: 'acc1' as PersonUuid })
|
||||
const results = await collection.find({ uuid: 'acc1' as AccountUuid })
|
||||
|
||||
expect(results[0].hash).toBeInstanceOf(Buffer)
|
||||
expect(results[0].salt).toBeInstanceOf(Buffer)
|
||||
@@ -331,7 +332,7 @@ describe('AccountMongoDbCollection', () => {
|
||||
const mockDocs = [
|
||||
{
|
||||
_id: 'id1',
|
||||
uuid: 'acc1' as PersonUuid,
|
||||
uuid: 'acc1' as AccountUuid,
|
||||
hash: null,
|
||||
salt: null
|
||||
}
|
||||
@@ -351,7 +352,7 @@ describe('AccountMongoDbCollection', () => {
|
||||
|
||||
mockCollection.find.mockReturnValue(mockCursor)
|
||||
|
||||
const results = await collection.find({ uuid: 'acc1' as PersonUuid })
|
||||
const results = await collection.find({ uuid: 'acc1' as AccountUuid })
|
||||
|
||||
expect(results[0].hash).toBeNull()
|
||||
expect(results[0].salt).toBeNull()
|
||||
@@ -362,14 +363,14 @@ describe('AccountMongoDbCollection', () => {
|
||||
it('should convert Buffer fields in found document', async () => {
|
||||
const mockDoc = {
|
||||
_id: 'id1',
|
||||
uuid: 'acc1' as PersonUuid,
|
||||
uuid: 'acc1' as AccountUuid,
|
||||
hash: { buffer: new Uint8Array([1, 2, 3]) },
|
||||
salt: { buffer: new Uint8Array([4, 5, 6]) }
|
||||
}
|
||||
|
||||
mockCollection.findOne.mockResolvedValue(mockDoc)
|
||||
|
||||
const result = await collection.findOne({ uuid: 'acc1' as PersonUuid })
|
||||
const result = await collection.findOne({ uuid: 'acc1' as AccountUuid })
|
||||
|
||||
expect(result?.hash).toBeInstanceOf(Buffer)
|
||||
expect(result?.salt).toBeInstanceOf(Buffer)
|
||||
@@ -380,14 +381,14 @@ describe('AccountMongoDbCollection', () => {
|
||||
it('should handle null hash and salt in found document', async () => {
|
||||
const mockDoc = {
|
||||
_id: 'id1',
|
||||
uuid: 'acc1' as PersonUuid,
|
||||
uuid: 'acc1' as AccountUuid,
|
||||
hash: null,
|
||||
salt: null
|
||||
}
|
||||
|
||||
mockCollection.findOne.mockResolvedValue(mockDoc)
|
||||
|
||||
const result = await collection.findOne({ uuid: 'acc1' as PersonUuid })
|
||||
const result = await collection.findOne({ uuid: 'acc1' as AccountUuid })
|
||||
|
||||
expect(result?.hash).toBeNull()
|
||||
expect(result?.salt).toBeNull()
|
||||
@@ -396,7 +397,7 @@ describe('AccountMongoDbCollection', () => {
|
||||
it('should handle null result', async () => {
|
||||
mockCollection.findOne.mockResolvedValue(null)
|
||||
|
||||
const result = await collection.findOne({ uuid: 'non-existent' as PersonUuid })
|
||||
const result = await collection.findOne({ uuid: 'non-existent' as AccountUuid })
|
||||
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
@@ -789,7 +790,7 @@ describe('MongoAccountDB', () => {
|
||||
})
|
||||
|
||||
describe('workspace operations', () => {
|
||||
const accountId = 'acc1' as PersonUuid
|
||||
const accountId = 'acc1' as AccountUuid
|
||||
const workspaceId = 'ws1' as WorkspaceUuid
|
||||
const role = AccountRole.Owner
|
||||
|
||||
@@ -851,7 +852,7 @@ describe('MongoAccountDB', () => {
|
||||
describe('getWorkspaceMembers', () => {
|
||||
it('should return mapped member info', async () => {
|
||||
const members = [
|
||||
{ accountUuid: 'acc1' as PersonUuid, role: AccountRole.Owner },
|
||||
{ accountUuid: 'acc1' as AccountUuid, role: AccountRole.Owner },
|
||||
{ accountUuid: 'acc2' as PersonUuid, role: AccountRole.Maintainer }
|
||||
]
|
||||
|
||||
@@ -1169,7 +1170,7 @@ describe('MongoAccountDB', () => {
|
||||
})
|
||||
|
||||
describe('password operations', () => {
|
||||
const accountId = 'acc1' as PersonUuid
|
||||
const accountId = 'acc1' as AccountUuid
|
||||
const passwordHash = Buffer.from('hash')
|
||||
const salt = Buffer.from('salt')
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
AccountRole,
|
||||
Data,
|
||||
Version,
|
||||
type PersonUuid,
|
||||
type AccountUuid,
|
||||
type WorkspaceMode,
|
||||
type WorkspaceUuid
|
||||
} from '@hcengineering/core'
|
||||
@@ -229,7 +229,7 @@ describe('AccountPostgresDbCollection', () => {
|
||||
it('should join with passwords table', async () => {
|
||||
const mockResult = [
|
||||
{
|
||||
uuid: 'acc1' as PersonUuid,
|
||||
uuid: 'acc1' as AccountUuid,
|
||||
timezone: 'UTC',
|
||||
locale: 'en',
|
||||
hash: null,
|
||||
@@ -238,7 +238,7 @@ describe('AccountPostgresDbCollection', () => {
|
||||
]
|
||||
mockClient.unsafe.mockResolvedValue(mockResult)
|
||||
|
||||
const result = await collection.find({ uuid: 'acc1' as PersonUuid })
|
||||
const result = await collection.find({ uuid: 'acc1' as AccountUuid })
|
||||
|
||||
expect(mockClient.unsafe).toHaveBeenCalledWith(
|
||||
`SELECT * FROM (
|
||||
@@ -260,7 +260,7 @@ describe('AccountPostgresDbCollection', () => {
|
||||
it('should convert buffer fields from database', async () => {
|
||||
const mockResult = [
|
||||
{
|
||||
uuid: 'acc1' as PersonUuid,
|
||||
uuid: 'acc1' as AccountUuid,
|
||||
timezone: 'UTC',
|
||||
locale: 'en',
|
||||
hash: { 0: 1, 1: 2, 3: 3 }, // Simulating buffer data from DB
|
||||
@@ -269,7 +269,7 @@ describe('AccountPostgresDbCollection', () => {
|
||||
]
|
||||
mockClient.unsafe.mockResolvedValue(mockResult)
|
||||
|
||||
const result = await collection.find({ uuid: 'acc1' as PersonUuid })
|
||||
const result = await collection.find({ uuid: 'acc1' as AccountUuid })
|
||||
|
||||
expect(result[0].hash).toBeInstanceOf(Buffer)
|
||||
expect(result[0].salt).toBeInstanceOf(Buffer)
|
||||
@@ -290,7 +290,7 @@ describe('AccountPostgresDbCollection', () => {
|
||||
describe('insertOne', () => {
|
||||
it('should prevent inserting password fields', async () => {
|
||||
const doc = {
|
||||
uuid: 'acc1' as PersonUuid,
|
||||
uuid: 'acc1' as AccountUuid,
|
||||
hash: Buffer.from([]),
|
||||
salt: Buffer.from([])
|
||||
}
|
||||
@@ -300,7 +300,7 @@ describe('AccountPostgresDbCollection', () => {
|
||||
|
||||
it('should allow inserting non-password fields', async () => {
|
||||
const doc = {
|
||||
uuid: 'acc1' as PersonUuid,
|
||||
uuid: 'acc1' as AccountUuid,
|
||||
timezone: 'UTC',
|
||||
locale: 'en'
|
||||
}
|
||||
@@ -324,12 +324,12 @@ describe('AccountPostgresDbCollection', () => {
|
||||
|
||||
it('should prevent updating password fields', async () => {
|
||||
await expect(
|
||||
collection.updateOne({ uuid: 'acc1' as PersonUuid }, { hash: Buffer.from([]), salt: Buffer.from([]) })
|
||||
collection.updateOne({ uuid: 'acc1' as AccountUuid }, { hash: Buffer.from([]), salt: Buffer.from([]) })
|
||||
).rejects.toThrow('Passwords are not allowed in update query')
|
||||
})
|
||||
|
||||
it('should allow updating non-password fields', async () => {
|
||||
await collection.updateOne({ uuid: 'acc1' as PersonUuid }, { timezone: 'UTC', locale: 'en' })
|
||||
await collection.updateOne({ uuid: 'acc1' as AccountUuid }, { timezone: 'UTC', locale: 'en' })
|
||||
|
||||
expect(mockClient.unsafe).toHaveBeenCalledWith(
|
||||
'UPDATE global_account.account SET "timezone" = $1, "locale" = $2 WHERE "uuid" = $3',
|
||||
@@ -346,7 +346,7 @@ describe('AccountPostgresDbCollection', () => {
|
||||
})
|
||||
|
||||
it('should allow deleting by non-password fields', async () => {
|
||||
await collection.deleteMany({ uuid: 'acc1' as PersonUuid })
|
||||
await collection.deleteMany({ uuid: 'acc1' as AccountUuid })
|
||||
|
||||
expect(mockClient.unsafe).toHaveBeenCalledWith('DELETE FROM global_account.account WHERE "uuid" = $1', ['acc1'])
|
||||
})
|
||||
@@ -399,7 +399,7 @@ describe('PostgresAccountDB', () => {
|
||||
})
|
||||
|
||||
describe('workspace operations', () => {
|
||||
const accountId = 'acc1' as PersonUuid
|
||||
const accountId = 'acc1' as AccountUuid
|
||||
const workspaceId = 'ws1' as WorkspaceUuid
|
||||
const role = AccountRole.Owner
|
||||
|
||||
@@ -877,7 +877,7 @@ describe('PostgresAccountDB', () => {
|
||||
})
|
||||
|
||||
describe('password operations', () => {
|
||||
const accountId = 'acc1' as PersonUuid
|
||||
const accountId = 'acc1' as AccountUuid
|
||||
const hash: any = {
|
||||
buffer: Buffer.from('hash')
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
import {
|
||||
AccountRole,
|
||||
type AccountUuid,
|
||||
Branding,
|
||||
MeasureContext,
|
||||
Person,
|
||||
@@ -938,6 +939,7 @@ describe('account utils', () => {
|
||||
|
||||
test('should confirm unverified email', async () => {
|
||||
const mockSocialId = {
|
||||
_id: '1000000001' as PersonId,
|
||||
key: 'email:test@example.com',
|
||||
type: SocialIdType.EMAIL,
|
||||
value: email,
|
||||
@@ -948,7 +950,7 @@ describe('account utils', () => {
|
||||
await confirmEmail(mockCtx, mockDb, account, email)
|
||||
|
||||
expect(mockDb.socialId.updateOne).toHaveBeenCalledWith(
|
||||
{ key: mockSocialId.key },
|
||||
{ _id: mockSocialId._id },
|
||||
{ verifiedOn: expect.any(Number) }
|
||||
)
|
||||
})
|
||||
@@ -1478,10 +1480,10 @@ describe('account utils', () => {
|
||||
})
|
||||
|
||||
test('should return account when found', async () => {
|
||||
const mockAccount = { uuid: 'test-uuid' as PersonUuid }
|
||||
const mockAccount = { uuid: 'test-uuid' as AccountUuid }
|
||||
;(mockDb.account.findOne as jest.Mock).mockResolvedValue(mockAccount)
|
||||
|
||||
const result = await getAccount(mockDb, 'test-uuid' as PersonUuid)
|
||||
const result = await getAccount(mockDb, 'test-uuid' as AccountUuid)
|
||||
expect(result).toEqual(mockAccount)
|
||||
expect(mockDb.account.findOne).toHaveBeenCalledWith({ uuid: 'test-uuid' })
|
||||
})
|
||||
@@ -1489,7 +1491,7 @@ describe('account utils', () => {
|
||||
test('should return null when account not found', async () => {
|
||||
;(mockDb.account.findOne as jest.Mock).mockResolvedValue(null)
|
||||
|
||||
const result = await getAccount(mockDb, 'nonexistent-uuid' as PersonUuid)
|
||||
const result = await getAccount(mockDb, 'nonexistent-uuid' as AccountUuid)
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -1726,6 +1728,7 @@ describe('account utils', () => {
|
||||
|
||||
const mockDb = {
|
||||
socialId: {
|
||||
find: jest.fn(() => []),
|
||||
findOne: jest.fn(),
|
||||
insertOne: jest.fn(),
|
||||
updateOne: jest.fn()
|
||||
|
||||
@@ -30,8 +30,8 @@ import {
|
||||
type AccountRole,
|
||||
type Data,
|
||||
type Version,
|
||||
type PersonUuid,
|
||||
type WorkspaceUuid
|
||||
type WorkspaceUuid,
|
||||
AccountUuid
|
||||
} from '@hcengineering/core'
|
||||
|
||||
import type {
|
||||
@@ -357,7 +357,7 @@ export class WorkspaceStatusMongoDbCollection implements DbCollection<WorkspaceS
|
||||
|
||||
interface WorkspaceMember {
|
||||
workspaceUuid: WorkspaceUuid
|
||||
accountUuid: PersonUuid
|
||||
accountUuid: AccountUuid
|
||||
role: AccountRole
|
||||
}
|
||||
|
||||
@@ -530,7 +530,7 @@ export class MongoAccountDB implements AccountDB {
|
||||
}
|
||||
}
|
||||
|
||||
async assignWorkspace (accountId: PersonUuid, workspaceId: WorkspaceUuid, role: AccountRole): Promise<void> {
|
||||
async assignWorkspace (accountId: AccountUuid, workspaceId: WorkspaceUuid, role: AccountRole): Promise<void> {
|
||||
await this.workspaceMembers.insertOne({
|
||||
workspaceUuid: workspaceId,
|
||||
accountUuid: accountId,
|
||||
@@ -538,7 +538,7 @@ export class MongoAccountDB implements AccountDB {
|
||||
})
|
||||
}
|
||||
|
||||
async unassignWorkspace (accountId: PersonUuid, workspaceId: WorkspaceUuid): Promise<void> {
|
||||
async unassignWorkspace (accountId: AccountUuid, workspaceId: WorkspaceUuid): Promise<void> {
|
||||
await this.workspaceMembers.deleteMany({
|
||||
workspaceUuid: workspaceId,
|
||||
accountUuid: accountId
|
||||
@@ -697,7 +697,7 @@ export class MongoAccountDB implements AccountDB {
|
||||
)
|
||||
}
|
||||
|
||||
async updateWorkspaceRole (accountId: PersonUuid, workspaceId: WorkspaceUuid, role: AccountRole): Promise<void> {
|
||||
async updateWorkspaceRole (accountId: AccountUuid, workspaceId: WorkspaceUuid, role: AccountRole): Promise<void> {
|
||||
await this.workspaceMembers.updateOne(
|
||||
{
|
||||
workspaceUuid: workspaceId,
|
||||
@@ -707,7 +707,7 @@ export class MongoAccountDB implements AccountDB {
|
||||
)
|
||||
}
|
||||
|
||||
async getWorkspaceRole (accountId: PersonUuid, workspaceId: WorkspaceUuid): Promise<AccountRole | null> {
|
||||
async getWorkspaceRole (accountId: AccountUuid, workspaceId: WorkspaceUuid): Promise<AccountRole | null> {
|
||||
const assignment = await this.workspaceMembers.findOne({
|
||||
workspaceUuid: workspaceId,
|
||||
accountUuid: accountId
|
||||
@@ -723,18 +723,18 @@ export class MongoAccountDB implements AccountDB {
|
||||
}))
|
||||
}
|
||||
|
||||
async getAccountWorkspaces (accountId: PersonUuid): Promise<WorkspaceInfoWithStatus[]> {
|
||||
async getAccountWorkspaces (accountId: AccountUuid): Promise<WorkspaceInfoWithStatus[]> {
|
||||
const members = await this.workspaceMembers.find({ accountUuid: accountId })
|
||||
const wsIds = members.map((m) => m.workspaceUuid)
|
||||
|
||||
return await this.workspace.find({ uuid: { $in: wsIds } })
|
||||
}
|
||||
|
||||
async setPassword (accountId: PersonUuid, passwordHash: Buffer, salt: Buffer): Promise<void> {
|
||||
async setPassword (accountId: AccountUuid, passwordHash: Buffer, salt: Buffer): Promise<void> {
|
||||
await this.account.updateOne({ uuid: accountId }, { hash: passwordHash, salt })
|
||||
}
|
||||
|
||||
async resetPassword (accountId: PersonUuid): Promise<void> {
|
||||
async resetPassword (accountId: AccountUuid): Promise<void> {
|
||||
await this.account.updateOne({ uuid: accountId }, { hash: null, salt: null })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ import {
|
||||
type Person,
|
||||
type WorkspaceMemberInfo,
|
||||
AccountRole,
|
||||
type PersonUuid,
|
||||
type WorkspaceUuid
|
||||
type WorkspaceUuid,
|
||||
type AccountUuid
|
||||
} from '@hcengineering/core'
|
||||
|
||||
import type {
|
||||
@@ -477,22 +477,22 @@ export class PostgresAccountDB implements AccountDB {
|
||||
})
|
||||
}
|
||||
|
||||
async assignWorkspace (accountUuid: PersonUuid, workspaceUuid: WorkspaceUuid, role: AccountRole): Promise<void> {
|
||||
async assignWorkspace (accountUuid: AccountUuid, workspaceUuid: WorkspaceUuid, role: AccountRole): Promise<void> {
|
||||
await this
|
||||
.client`INSERT INTO ${this.client(this.getWsMembersTableName())} (workspace_uuid, account_uuid, role) VALUES (${workspaceUuid}, ${accountUuid}, ${role})`
|
||||
}
|
||||
|
||||
async unassignWorkspace (accountUuid: PersonUuid, workspaceUuid: WorkspaceUuid): Promise<void> {
|
||||
async unassignWorkspace (accountUuid: AccountUuid, workspaceUuid: WorkspaceUuid): Promise<void> {
|
||||
await this
|
||||
.client`DELETE FROM ${this.client(this.getWsMembersTableName())} WHERE workspace_uuid = ${workspaceUuid} AND account_uuid = ${accountUuid}`
|
||||
}
|
||||
|
||||
async updateWorkspaceRole (accountUuid: PersonUuid, workspaceUuid: WorkspaceUuid, role: AccountRole): Promise<void> {
|
||||
async updateWorkspaceRole (accountUuid: AccountUuid, workspaceUuid: WorkspaceUuid, role: AccountRole): Promise<void> {
|
||||
await this
|
||||
.client`UPDATE ${this.client(this.getWsMembersTableName())} SET role = ${role} WHERE workspace_uuid = ${workspaceUuid} AND account_uuid = ${accountUuid}`
|
||||
}
|
||||
|
||||
async getWorkspaceRole (accountUuid: PersonUuid, workspaceUuid: WorkspaceUuid): Promise<AccountRole | null> {
|
||||
async getWorkspaceRole (accountUuid: AccountUuid, workspaceUuid: WorkspaceUuid): Promise<AccountRole | null> {
|
||||
const res: any = await this
|
||||
.client`SELECT role FROM ${this.client(this.getWsMembersTableName())} WHERE workspace_uuid = ${workspaceUuid} AND account_uuid = ${accountUuid}`
|
||||
|
||||
@@ -509,7 +509,7 @@ export class PostgresAccountDB implements AccountDB {
|
||||
}))
|
||||
}
|
||||
|
||||
async getAccountWorkspaces (accountUuid: PersonUuid): Promise<WorkspaceInfoWithStatus[]> {
|
||||
async getAccountWorkspaces (accountUuid: AccountUuid): Promise<WorkspaceInfoWithStatus[]> {
|
||||
const sql = `SELECT
|
||||
w.uuid,
|
||||
w.name,
|
||||
@@ -656,12 +656,12 @@ export class PostgresAccountDB implements AccountDB {
|
||||
return convertKeysToCamelCase(res[0]) as WorkspaceInfoWithStatus
|
||||
}
|
||||
|
||||
async setPassword (accountUuid: PersonUuid, hash: Buffer, salt: Buffer): Promise<void> {
|
||||
async setPassword (accountUuid: AccountUuid, hash: Buffer, salt: Buffer): Promise<void> {
|
||||
await this
|
||||
.client`UPSERT INTO ${this.client(this.account.getPasswordsTableName())} (account_uuid, hash, salt) VALUES (${accountUuid}, ${hash.buffer as any}::bytea, ${salt.buffer as any}::bytea)`
|
||||
}
|
||||
|
||||
async resetPassword (accountUuid: PersonUuid): Promise<void> {
|
||||
async resetPassword (accountUuid: AccountUuid): Promise<void> {
|
||||
await this
|
||||
.client`DELETE FROM ${this.client(this.account.getPasswordsTableName())} WHERE account_uuid = ${accountUuid}`
|
||||
}
|
||||
|
||||
@@ -26,10 +26,10 @@ import {
|
||||
type Branding,
|
||||
type Person,
|
||||
type PersonId,
|
||||
type PersonInfo,
|
||||
type PersonUuid,
|
||||
type WorkspaceMemberInfo,
|
||||
type WorkspaceUuid
|
||||
type WorkspaceUuid,
|
||||
type AccountUuid
|
||||
} from '@hcengineering/core'
|
||||
import platform, { getMetadata, PlatformError, Severity, Status, translate } from '@hcengineering/platform'
|
||||
import { decodeTokenVerbose, generateToken } from '@hcengineering/server-token'
|
||||
@@ -89,7 +89,8 @@ import {
|
||||
addSocialId,
|
||||
releaseSocialId,
|
||||
updateWorkspaceRole,
|
||||
setTimezoneIfNotDefined
|
||||
setTimezoneIfNotDefined,
|
||||
confirmHulyIds
|
||||
} from './utils'
|
||||
import { type AccountServiceMethods, getServiceMethods } from './serviceOperations'
|
||||
|
||||
@@ -128,7 +129,7 @@ export async function login (
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotFound, {}))
|
||||
}
|
||||
|
||||
const existingAccount = await db.account.findOne({ uuid: emailSocialId.personUuid })
|
||||
const existingAccount = await db.account.findOne({ uuid: emailSocialId.personUuid as AccountUuid })
|
||||
|
||||
if (existingAccount == null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotFound, {}))
|
||||
@@ -147,7 +148,7 @@ export async function login (
|
||||
|
||||
const extraToken: Record<string, string> = isAdminEmail(email) ? { admin: 'true' } : {}
|
||||
ctx.info('Login succeeded', { email, normalizedEmail, isConfirmed, emailSocialId, ...extraToken })
|
||||
void setTimezoneIfNotDefined(ctx, db, emailSocialId.personUuid, existingAccount, meta)
|
||||
void setTimezoneIfNotDefined(ctx, db, existingAccount.uuid, existingAccount, meta)
|
||||
|
||||
return {
|
||||
account: existingAccount.uuid,
|
||||
@@ -183,13 +184,13 @@ export async function loginOtp (
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotFound, {}))
|
||||
}
|
||||
|
||||
const account = await getAccount(db, emailSocialId.personUuid)
|
||||
const account = await getAccount(db, emailSocialId.personUuid as AccountUuid)
|
||||
|
||||
if (account == null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotFound, {}))
|
||||
}
|
||||
|
||||
void setTimezoneIfNotDefined(ctx, db, emailSocialId.personUuid, account, meta)
|
||||
void setTimezoneIfNotDefined(ctx, db, account.uuid, account, meta)
|
||||
|
||||
return await sendOtp(ctx, db, branding, emailSocialId)
|
||||
}
|
||||
@@ -247,8 +248,7 @@ export async function signUpOtp (
|
||||
email: string
|
||||
firstName: string
|
||||
lastName: string
|
||||
},
|
||||
meta?: Meta
|
||||
}
|
||||
): Promise<OtpInfo> {
|
||||
const { email, firstName, lastName } = params
|
||||
// Note: can support OTP based on any other social logins later
|
||||
@@ -257,7 +257,7 @@ export async function signUpOtp (
|
||||
let personUuid: PersonUuid
|
||||
|
||||
if (emailSocialId !== null) {
|
||||
const existingAccount = await db.account.findOne({ uuid: emailSocialId.personUuid })
|
||||
const existingAccount = await db.account.findOne({ uuid: emailSocialId.personUuid as AccountUuid })
|
||||
|
||||
if (existingAccount !== null) {
|
||||
ctx.error('An account with the provided email already exists', { email })
|
||||
@@ -274,7 +274,6 @@ export async function signUpOtp (
|
||||
const emailSocialIdId = await db.socialId.insertOne(newSocialId)
|
||||
emailSocialId = { ...newSocialId, _id: emailSocialIdId, key: buildSocialIdString(newSocialId) }
|
||||
}
|
||||
void setTimezoneIfNotDefined(ctx, db, personUuid, null, meta)
|
||||
|
||||
return await sendOtp(ctx, db, branding, emailSocialId)
|
||||
}
|
||||
@@ -287,7 +286,8 @@ export async function validateOtp (
|
||||
params: {
|
||||
email: string
|
||||
code: string
|
||||
}
|
||||
},
|
||||
meta?: Meta
|
||||
): Promise<LoginInfo> {
|
||||
const { email, code } = params
|
||||
|
||||
@@ -312,7 +312,7 @@ export async function validateOtp (
|
||||
}
|
||||
|
||||
// This method handles both login and signup
|
||||
const account = await db.account.findOne({ uuid: emailSocialId.personUuid })
|
||||
const account = await db.account.findOne({ uuid: emailSocialId.personUuid as AccountUuid })
|
||||
|
||||
if (account == null) {
|
||||
// This is a signup
|
||||
@@ -320,18 +320,20 @@ export async function validateOtp (
|
||||
|
||||
ctx.info('OTP signup success', emailSocialId)
|
||||
} else {
|
||||
// Confirm huly social id if hasn't been confirmed yet
|
||||
await confirmHulyIds(ctx, db, account.uuid)
|
||||
|
||||
ctx.info('OTP login success', emailSocialId)
|
||||
}
|
||||
|
||||
void setTimezoneIfNotDefined(ctx, db, emailSocialId.personUuid as AccountUuid, account, meta)
|
||||
|
||||
const person = await db.person.findOne({ uuid: emailSocialId.personUuid })
|
||||
if (person == null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.InternalServerError, {}))
|
||||
}
|
||||
|
||||
return {
|
||||
account: emailSocialId.personUuid,
|
||||
account: emailSocialId.personUuid as AccountUuid,
|
||||
name: getPersonName(person),
|
||||
socialId: emailSocialId._id,
|
||||
token: generateToken(emailSocialId.personUuid)
|
||||
@@ -764,7 +766,7 @@ export async function checkAutoJoin (
|
||||
|
||||
// If it's an existing account we should check for saved token or ask for login to prevent accidental access through shared link
|
||||
if (emailSocialId != null) {
|
||||
const targetAccount = await getAccount(db, emailSocialId.personUuid)
|
||||
const targetAccount = await getAccount(db, emailSocialId.personUuid as AccountUuid)
|
||||
if (targetAccount != null) {
|
||||
if (targetAccount.automatic == null || !targetAccount.automatic) {
|
||||
if (token == null) {
|
||||
@@ -883,6 +885,8 @@ export async function confirm (
|
||||
|
||||
const socialId = await confirmEmail(ctx, db, account, email)
|
||||
|
||||
await confirmHulyIds(ctx, db, account)
|
||||
|
||||
const person = await db.person.findOne({ uuid: account })
|
||||
if (person == null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.InternalServerError, {}))
|
||||
@@ -951,7 +955,7 @@ export async function requestPasswordReset (
|
||||
)
|
||||
}
|
||||
|
||||
const account = await getAccount(db, emailSocialId.personUuid)
|
||||
const account = await getAccount(db, emailSocialId.personUuid as AccountUuid)
|
||||
|
||||
if (account == null) {
|
||||
ctx.info('Account not found', { email, normalizedEmail })
|
||||
@@ -1038,7 +1042,7 @@ export async function leaveWorkspace (
|
||||
db: AccountDB,
|
||||
branding: Branding | null,
|
||||
token: string,
|
||||
params: { account: PersonUuid }
|
||||
params: { account: AccountUuid }
|
||||
): Promise<LoginInfo | null> {
|
||||
const { account: targetAccount } = params
|
||||
const { account, workspace } = decodeTokenVerbose(ctx, token)
|
||||
@@ -1254,7 +1258,7 @@ export async function getLoginInfoByToken (
|
||||
branding: Branding | null,
|
||||
token: string
|
||||
): Promise<LoginInfo | WorkspaceLoginInfo> {
|
||||
let accountUuid: PersonUuid
|
||||
let accountUuid: AccountUuid
|
||||
let workspaceUuid: WorkspaceUuid
|
||||
let extra: any
|
||||
try {
|
||||
@@ -1382,32 +1386,6 @@ export async function getPerson (
|
||||
return person
|
||||
}
|
||||
|
||||
export async function getPersonInfo (
|
||||
ctx: MeasureContext,
|
||||
db: AccountDB,
|
||||
branding: Branding | null,
|
||||
token: string,
|
||||
params: { account: PersonUuid }
|
||||
): Promise<PersonInfo> {
|
||||
const { account } = params
|
||||
const { extra } = decodeTokenVerbose(ctx, token)
|
||||
verifyAllowedServices(['workspace', 'tool'], extra)
|
||||
|
||||
const person = await db.person.findOne({ uuid: account })
|
||||
|
||||
if (person == null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.PersonNotFound, { person: account }))
|
||||
}
|
||||
|
||||
const verifiedSocialIds = await db.socialId.find({ personUuid: account, verifiedOn: { $gt: 0 } })
|
||||
|
||||
return {
|
||||
personUuid: account,
|
||||
name: getPersonName(person),
|
||||
socialIds: verifiedSocialIds.map((it) => it._id)
|
||||
}
|
||||
}
|
||||
|
||||
export async function findPersonBySocialKey (
|
||||
ctx: MeasureContext,
|
||||
db: AccountDB,
|
||||
@@ -1425,7 +1403,7 @@ export async function findPersonBySocialKey (
|
||||
}
|
||||
|
||||
if (params.requireAccount === true) {
|
||||
const account = await db.account.findOne({ uuid: socialId.personUuid })
|
||||
const account = await db.account.findOne({ uuid: socialId.personUuid as AccountUuid })
|
||||
|
||||
return account?.uuid
|
||||
}
|
||||
@@ -1451,7 +1429,7 @@ export async function findPersonBySocialId (
|
||||
|
||||
// TODO: combine into one request with join
|
||||
if (requireAccount === true) {
|
||||
const account = await db.account.findOne({ uuid: socialIdObj.personUuid })
|
||||
const account = await db.account.findOne({ uuid: socialIdObj.personUuid as AccountUuid })
|
||||
if (account == null) {
|
||||
return
|
||||
}
|
||||
@@ -1478,7 +1456,7 @@ export async function findSocialIdBySocialKey (
|
||||
|
||||
// TODO: combine into one request with join
|
||||
if (requireAccount === true) {
|
||||
const account = await db.account.findOne({ uuid: socialIdObj.personUuid })
|
||||
const account = await db.account.findOne({ uuid: socialIdObj.personUuid as AccountUuid })
|
||||
if (account == null) {
|
||||
return
|
||||
}
|
||||
@@ -1513,7 +1491,7 @@ export async function getAccountInfo (
|
||||
db: AccountDB,
|
||||
branding: Branding | null,
|
||||
token: string,
|
||||
params: { accountId: PersonUuid }
|
||||
params: { accountId: AccountUuid }
|
||||
): Promise<AccountInfo> {
|
||||
decodeTokenVerbose(ctx, token)
|
||||
const { accountId } = params
|
||||
@@ -1689,7 +1667,6 @@ export type AccountMethods =
|
||||
| 'getLoginInfoByToken'
|
||||
| 'getSocialIds'
|
||||
| 'getPerson'
|
||||
| 'getPersonInfo'
|
||||
| 'getWorkspaceMembers'
|
||||
| 'updateWorkspaceRole'
|
||||
| 'findPersonBySocialKey'
|
||||
@@ -1746,7 +1723,6 @@ export function getMethods (hasSignUp: boolean = true): Partial<Record<AccountMe
|
||||
getLoginInfoByToken: wrap(getLoginInfoByToken),
|
||||
getSocialIds: wrap(getSocialIds),
|
||||
getPerson: wrap(getPerson),
|
||||
getPersonInfo: wrap(getPersonInfo),
|
||||
findPersonBySocialKey: wrap(findPersonBySocialKey),
|
||||
findPersonBySocialId: wrap(findPersonBySocialId),
|
||||
findSocialIdBySocialKey: wrap(findSocialIdBySocialKey),
|
||||
|
||||
@@ -20,11 +20,13 @@ import {
|
||||
SocialIdType,
|
||||
Version,
|
||||
WorkspaceMode,
|
||||
type PersonInfo,
|
||||
type BackupStatus,
|
||||
type Branding,
|
||||
type PersonId,
|
||||
type PersonUuid,
|
||||
type WorkspaceUuid
|
||||
type WorkspaceUuid,
|
||||
type AccountUuid
|
||||
} from '@hcengineering/core'
|
||||
import platform, { getMetadata, PlatformError, Severity, Status, unknownError } from '@hcengineering/platform'
|
||||
import { decodeTokenVerbose } from '@hcengineering/server-token'
|
||||
@@ -57,7 +59,8 @@ import {
|
||||
wrap,
|
||||
addSocialId,
|
||||
getWorkspaces,
|
||||
updateWorkspaceRole
|
||||
updateWorkspaceRole,
|
||||
getPersonName
|
||||
} from './utils'
|
||||
|
||||
// Note: it is IMPORTANT to always destructure params passed here to avoid sending extra params
|
||||
@@ -201,17 +204,14 @@ export async function updateWorkspaceRoleBySocialKey (
|
||||
): Promise<void> {
|
||||
const { socialKey, targetRole } = params
|
||||
const { extra } = decodeTokenVerbose(ctx, token)
|
||||
|
||||
if (!['workspace', 'tool'].includes(extra?.service)) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
||||
}
|
||||
verifyAllowedServices(['workspace', 'tool'], extra)
|
||||
|
||||
const socialId = await getSocialIdByKey(db, socialKey.toLowerCase() as PersonId)
|
||||
if (socialId == null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotFound, {}))
|
||||
}
|
||||
|
||||
await updateWorkspaceRole(ctx, db, branding, token, { targetAccount: socialId.personUuid, targetRole })
|
||||
await updateWorkspaceRole(ctx, db, branding, token, { targetAccount: socialId.personUuid as AccountUuid, targetRole })
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -468,7 +468,7 @@ export async function assignWorkspace (
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotFound, {}))
|
||||
}
|
||||
|
||||
const account = await getAccount(db, emailSocialId.personUuid)
|
||||
const account = await getAccount(db, emailSocialId.personUuid as AccountUuid)
|
||||
|
||||
if (account == null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotFound, {}))
|
||||
@@ -489,6 +489,32 @@ export async function assignWorkspace (
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPersonInfo (
|
||||
ctx: MeasureContext,
|
||||
db: AccountDB,
|
||||
branding: Branding | null,
|
||||
token: string,
|
||||
params: { account: PersonUuid }
|
||||
): Promise<PersonInfo> {
|
||||
const { account } = params
|
||||
const { extra } = decodeTokenVerbose(ctx, token)
|
||||
verifyAllowedServices(['workspace', 'tool'], extra)
|
||||
|
||||
const person = await db.person.findOne({ uuid: account })
|
||||
|
||||
if (person == null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.PersonNotFound, { person: account }))
|
||||
}
|
||||
|
||||
const verifiedSocialIds = await db.socialId.find({ personUuid: account, verifiedOn: { $gt: 0 } })
|
||||
|
||||
return {
|
||||
personUuid: account,
|
||||
name: getPersonName(person),
|
||||
socialIds: verifiedSocialIds
|
||||
}
|
||||
}
|
||||
|
||||
export async function addSocialIdToPerson (
|
||||
ctx: MeasureContext,
|
||||
db: AccountDB,
|
||||
@@ -779,6 +805,7 @@ export type AccountServiceMethods =
|
||||
| 'performWorkspaceOperation'
|
||||
| 'updateWorkspaceRoleBySocialKey'
|
||||
| 'addSocialIdToPerson'
|
||||
| 'getPersonInfo'
|
||||
| 'createIntegration'
|
||||
| 'updateIntegration'
|
||||
| 'deleteIntegration'
|
||||
@@ -804,6 +831,7 @@ export function getServiceMethods (): Partial<Record<AccountServiceMethods, Acco
|
||||
performWorkspaceOperation: wrap(performWorkspaceOperation),
|
||||
updateWorkspaceRoleBySocialKey: wrap(updateWorkspaceRoleBySocialKey),
|
||||
addSocialIdToPerson: wrap(addSocialIdToPerson),
|
||||
getPersonInfo: wrap(getPersonInfo),
|
||||
createIntegration: wrap(createIntegration),
|
||||
updateIntegration: wrap(updateIntegration),
|
||||
deleteIntegration: wrap(deleteIntegration),
|
||||
|
||||
+12
-11
@@ -27,7 +27,8 @@ import {
|
||||
type PersonUuid,
|
||||
type WorkspaceUuid,
|
||||
type WorkspaceDataId,
|
||||
type PersonId
|
||||
type PersonId,
|
||||
type AccountUuid
|
||||
} from '@hcengineering/core'
|
||||
|
||||
/* ========= D A T A B A S E E N T I T I E S ========= */
|
||||
@@ -50,7 +51,7 @@ export interface SocialId extends SocialIdBase {
|
||||
}
|
||||
|
||||
export interface Account {
|
||||
uuid: PersonUuid
|
||||
uuid: AccountUuid
|
||||
automatic?: boolean
|
||||
timezone?: string
|
||||
locale?: string
|
||||
@@ -60,7 +61,7 @@ export interface Account {
|
||||
|
||||
// TODO: type data with generic type
|
||||
export interface AccountEvent {
|
||||
accountUuid: PersonUuid
|
||||
accountUuid: AccountUuid
|
||||
eventType: AccountEventType
|
||||
data?: Record<string, any>
|
||||
time: Timestamp
|
||||
@@ -190,12 +191,12 @@ export interface AccountDB {
|
||||
|
||||
init: () => Promise<void>
|
||||
createWorkspace: (data: WorkspaceData, status: WorkspaceStatusData) => Promise<WorkspaceUuid>
|
||||
assignWorkspace: (accountId: PersonUuid, workspaceId: WorkspaceUuid, role: AccountRole) => Promise<void>
|
||||
updateWorkspaceRole: (accountId: PersonUuid, workspaceId: WorkspaceUuid, role: AccountRole) => Promise<void>
|
||||
unassignWorkspace: (accountId: PersonUuid, workspaceId: WorkspaceUuid) => Promise<void>
|
||||
getWorkspaceRole: (accountId: PersonUuid, workspaceId: WorkspaceUuid) => Promise<AccountRole | null>
|
||||
assignWorkspace: (accountId: AccountUuid, workspaceId: WorkspaceUuid, role: AccountRole) => Promise<void>
|
||||
updateWorkspaceRole: (accountId: AccountUuid, workspaceId: WorkspaceUuid, role: AccountRole) => Promise<void>
|
||||
unassignWorkspace: (accountId: AccountUuid, workspaceId: WorkspaceUuid) => Promise<void>
|
||||
getWorkspaceRole: (accountId: AccountUuid, workspaceId: WorkspaceUuid) => Promise<AccountRole | null>
|
||||
getWorkspaceMembers: (workspaceId: WorkspaceUuid) => Promise<WorkspaceMemberInfo[]>
|
||||
getAccountWorkspaces: (accountId: PersonUuid) => Promise<WorkspaceInfoWithStatus[]>
|
||||
getAccountWorkspaces: (accountId: AccountUuid) => Promise<WorkspaceInfoWithStatus[]>
|
||||
getPendingWorkspace: (
|
||||
region: string,
|
||||
version: Data<Version>,
|
||||
@@ -203,8 +204,8 @@ export interface AccountDB {
|
||||
processingTimeoutMs: number,
|
||||
wsLivenessMs?: number
|
||||
) => Promise<WorkspaceInfoWithStatus | undefined>
|
||||
setPassword: (accountId: PersonUuid, passwordHash: Buffer, salt: Buffer) => Promise<void>
|
||||
resetPassword: (accountId: PersonUuid) => Promise<void>
|
||||
setPassword: (accountId: AccountUuid, passwordHash: Buffer, salt: Buffer) => Promise<void>
|
||||
resetPassword: (accountId: AccountUuid) => Promise<void>
|
||||
}
|
||||
|
||||
export interface DbCollection<T> {
|
||||
@@ -268,7 +269,7 @@ export type WorkspaceEvent =
|
||||
| 'archiving-done'
|
||||
export type WorkspaceOperation = 'create' | 'upgrade' | 'all' | 'all+backup'
|
||||
export interface LoginInfo {
|
||||
account: PersonUuid
|
||||
account: AccountUuid
|
||||
name?: string
|
||||
socialId?: PersonId
|
||||
token?: string
|
||||
|
||||
+27
-19
@@ -29,7 +29,8 @@ import {
|
||||
isActiveMode,
|
||||
type PersonUuid,
|
||||
type PersonId,
|
||||
type Person
|
||||
type Person,
|
||||
AccountUuid
|
||||
} from '@hcengineering/core'
|
||||
import { getMongoClient } from '@hcengineering/mongo' // TODO: get rid of this import later
|
||||
import platform, { getMetadata, PlatformError, Severity, Status, translate } from '@hcengineering/platform'
|
||||
@@ -328,7 +329,7 @@ export async function setPassword (
|
||||
ctx: MeasureContext,
|
||||
db: AccountDB,
|
||||
branding: Branding | null,
|
||||
personUuid: PersonUuid,
|
||||
personUuid: AccountUuid,
|
||||
password: string
|
||||
): Promise<void> {
|
||||
if (password == null || password === '') {
|
||||
@@ -444,17 +445,15 @@ export async function createAccount (
|
||||
createdOn = Date.now()
|
||||
): Promise<void> {
|
||||
// Create Huly social id and account
|
||||
// Currently, it's always created along with the account but never confirmed.
|
||||
// What's the actual use case for it?
|
||||
await db.socialId.insertOne({
|
||||
type: SocialIdType.HULY,
|
||||
value: personUuid,
|
||||
personUuid,
|
||||
...(confirmed ? { verifiedOn: Date.now() } : {})
|
||||
})
|
||||
await db.account.insertOne({ uuid: personUuid, automatic })
|
||||
await db.account.insertOne({ uuid: personUuid as AccountUuid, automatic })
|
||||
await db.accountEvent.insertOne({
|
||||
accountUuid: personUuid,
|
||||
accountUuid: personUuid as AccountUuid,
|
||||
eventType: AccountEventType.ACCOUNT_CREATED,
|
||||
time: createdOn
|
||||
})
|
||||
@@ -470,22 +469,22 @@ export async function signUpByEmail (
|
||||
lastName: string,
|
||||
confirmed = false,
|
||||
automatic = false
|
||||
): Promise<{ account: PersonUuid, socialId: PersonId }> {
|
||||
): Promise<{ account: AccountUuid, socialId: PersonId }> {
|
||||
const normalizedEmail = cleanEmail(email)
|
||||
|
||||
const emailSocialId = await getEmailSocialId(db, normalizedEmail)
|
||||
let account: PersonUuid
|
||||
let account: AccountUuid
|
||||
let socialId: PersonId
|
||||
|
||||
if (emailSocialId !== null) {
|
||||
const existingAccount = await db.account.findOne({ uuid: emailSocialId.personUuid })
|
||||
const existingAccount = await db.account.findOne({ uuid: emailSocialId.personUuid as AccountUuid })
|
||||
|
||||
if (existingAccount !== null) {
|
||||
ctx.error('An account with the provided email already exists', { email })
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountAlreadyExists, {}))
|
||||
}
|
||||
|
||||
account = emailSocialId.personUuid
|
||||
account = emailSocialId.personUuid as AccountUuid
|
||||
socialId = emailSocialId._id
|
||||
// Person exists, but may have different name, need to update with what's been provided
|
||||
await db.person.updateOne({ uuid: account }, { firstName, lastName })
|
||||
@@ -621,7 +620,7 @@ export async function updateWorkspaceRole (
|
||||
branding: Branding | null,
|
||||
token: string,
|
||||
params: {
|
||||
targetAccount: PersonUuid
|
||||
targetAccount: AccountUuid
|
||||
targetRole: AccountRole
|
||||
}
|
||||
): Promise<void> {
|
||||
@@ -899,15 +898,22 @@ export async function confirmEmail (
|
||||
)
|
||||
}
|
||||
|
||||
await db.socialId.updateOne({ key: emailSocialId.key }, { verifiedOn: Date.now() })
|
||||
await db.socialId.updateOne({ _id: emailSocialId._id }, { verifiedOn: Date.now() })
|
||||
return emailSocialId._id
|
||||
}
|
||||
|
||||
export async function confirmHulyIds (ctx: MeasureContext, db: AccountDB, account: AccountUuid): Promise<void> {
|
||||
const hulySocialIds = await db.socialId.find({ personUuid: account, type: SocialIdType.HULY, verifiedOn: null })
|
||||
for (const hulySocialId of hulySocialIds) {
|
||||
await db.socialId.updateOne({ _id: hulySocialId._id }, { verifiedOn: Date.now() })
|
||||
}
|
||||
}
|
||||
|
||||
export async function useInvite (db: AccountDB, inviteId: string): Promise<void> {
|
||||
await db.invite.updateOne({ id: inviteId }, { $inc: { remainingUses: -1 } })
|
||||
}
|
||||
|
||||
export async function getAccount (db: AccountDB, uuid: PersonUuid): Promise<Account | null> {
|
||||
export async function getAccount (db: AccountDB, uuid: AccountUuid): Promise<Account | null> {
|
||||
return await db.account.findOne({ uuid })
|
||||
}
|
||||
|
||||
@@ -1016,7 +1022,7 @@ export async function doJoinByInvite (
|
||||
db: AccountDB,
|
||||
branding: Branding | null,
|
||||
token: string,
|
||||
account: PersonUuid,
|
||||
account: AccountUuid,
|
||||
workspace: Workspace,
|
||||
invite: WorkspaceInvite
|
||||
): Promise<WorkspaceLoginInfo> {
|
||||
@@ -1079,7 +1085,7 @@ export async function loginOrSignUpWithProvider (
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.InternalServerError, {}))
|
||||
}
|
||||
|
||||
const account = await db.account.findOne({ uuid: personUuid })
|
||||
const account = await db.account.findOne({ uuid: personUuid as AccountUuid })
|
||||
|
||||
if (account == null) {
|
||||
if (signUpDisabled) {
|
||||
@@ -1095,7 +1101,7 @@ export async function loginOrSignUpWithProvider (
|
||||
const confirmedSocialId = await db.socialId.findOne({ personUuid, verifiedOn: { $gt: 0 } })
|
||||
|
||||
if (confirmedSocialId == null) {
|
||||
await db.resetPassword(personUuid)
|
||||
await db.resetPassword(personUuid as AccountUuid)
|
||||
}
|
||||
|
||||
let socialIdId: PersonId | undefined
|
||||
@@ -1120,8 +1126,10 @@ export async function loginOrSignUpWithProvider (
|
||||
await db.socialId.updateOne({ key: emailSocialId.key }, { verifiedOn: Date.now() })
|
||||
}
|
||||
|
||||
await confirmHulyIds(ctx, db, personUuid as AccountUuid)
|
||||
|
||||
return {
|
||||
account: personUuid,
|
||||
account: personUuid as AccountUuid,
|
||||
socialId: socialIdId,
|
||||
name: getPersonName(person),
|
||||
token: generateToken(personUuid)
|
||||
@@ -1376,7 +1384,7 @@ export async function releaseSocialId (
|
||||
|
||||
export async function getWorkspaceRole (
|
||||
db: AccountDB,
|
||||
account: PersonUuid,
|
||||
account: AccountUuid,
|
||||
workspace: WorkspaceUuid
|
||||
): Promise<AccountRole | null> {
|
||||
if (account === systemAccountUuid) {
|
||||
@@ -1393,7 +1401,7 @@ export function generatePassword (len: number = 24): string {
|
||||
export async function setTimezoneIfNotDefined (
|
||||
ctx: MeasureContext,
|
||||
db: AccountDB,
|
||||
accountId: PersonUuid,
|
||||
accountId: AccountUuid,
|
||||
account: Account | null | undefined,
|
||||
meta?: Meta
|
||||
): Promise<void> {
|
||||
|
||||
@@ -9,12 +9,13 @@ import core, {
|
||||
MeasureContext,
|
||||
Mixin,
|
||||
parseSocialIdString,
|
||||
type PersonId,
|
||||
type PersonInfo,
|
||||
Ref,
|
||||
SocialIdType,
|
||||
Space,
|
||||
TxOperations,
|
||||
pickPrimarySocialId,
|
||||
type PersonId,
|
||||
type PersonInfo,
|
||||
type WorkspaceIds
|
||||
} from '@hcengineering/core'
|
||||
import { ModelLogger } from '@hcengineering/model'
|
||||
@@ -23,7 +24,6 @@ import { HulyFormatImporter, StorageFileUploader } from '@hcengineering/importer
|
||||
import type { StorageAdapter } from '@hcengineering/server-core'
|
||||
import { jsonToMarkup } from '@hcengineering/text'
|
||||
import { markdownToMarkup } from '@hcengineering/text-markdown'
|
||||
import { pickPrimarySocialId } from '@hcengineering/contact'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import path from 'path'
|
||||
|
||||
@@ -114,7 +114,7 @@ export class WorkspaceInitializer {
|
||||
private readonly initRepoDir: string,
|
||||
private readonly creator: PersonInfo
|
||||
) {
|
||||
this.socialKey = pickPrimarySocialId(creator.socialIds)
|
||||
this.socialKey = pickPrimarySocialId(creator.socialIds)._id
|
||||
const socialKeyObj = parseSocialIdString(this.socialKey)
|
||||
this.socialType = socialKeyObj.type
|
||||
this.socialValue = socialKeyObj.value
|
||||
|
||||
@@ -178,7 +178,8 @@ export class LoveController {
|
||||
if (!this.socialIdByPerson.has(person)) {
|
||||
const identities = await this.client.findAll(contact.class.SocialIdentity, {
|
||||
attachedTo: person,
|
||||
attachedToClass: contact.class.Person
|
||||
attachedToClass: contact.class.Person,
|
||||
verifiedOn: { $gt: 0 }
|
||||
})
|
||||
if (identities.length > 0) {
|
||||
const id = pickPrimarySocialId(identities)._id
|
||||
|
||||
Reference in New Issue
Block a user