From 070ff5f596a35d0236ab24865e7164ead7060612 Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Wed, 22 May 2024 00:10:02 +0700 Subject: [PATCH] UBERF-7011: Switch to Ref Signed-off-by: Andrey Sobolev --- common/config/rush/pnpm-lock.yaml | 61 +++- dev/tool/src/clean.ts | 30 +- models/attachment/src/index.ts | 12 +- models/contact/src/index.ts | 31 +- models/contact/src/migration.ts | 60 ++- models/core/src/core.ts | 4 +- models/core/src/index.ts | 4 +- models/presentation/src/index.ts | 8 +- models/server-contact/src/index.ts | 4 +- models/server-recruit/src/index.ts | 4 +- models/setting/src/index.ts | 4 +- models/view/src/index.ts | 2 +- packages/core/src/classes.ts | 16 + packages/core/src/component.ts | 2 +- packages/core/src/server.ts | 35 +- packages/core/src/utils.ts | 2 +- packages/model/src/dsl.ts | 4 +- .../src/components/FilePreviewPopup.svelte | 11 +- .../src/components/PDFViewer.svelte | 10 +- packages/presentation/src/file.ts | 6 +- packages/presentation/src/utils.ts | 124 ++++++- packages/storage/src/index.ts | 23 +- .../components/CollaborativeTextEditor.svelte | 23 +- .../src/components/ImageStyleToolbar.svelte | 2 +- .../src/components/StyledTextBox.svelte | 17 +- .../src/components/extension/fileExt.ts | 2 +- .../src/components/extension/imageExt.ts | 39 +- .../components/extension/imageUploadExt.ts | 21 +- .../src/components/extension/types.ts | 4 +- packages/text-editor/src/kits/editor-kit.ts | 10 +- packages/text-editor/src/provider/minio.ts | 4 +- packages/text/src/nodes/image.ts | 16 +- .../src/components/BasePreview.svelte | 8 +- .../src/components/Replies.svelte | 2 +- .../ActivityInfoMessagePresenter.svelte | 2 +- .../ActivityMessageTemplate.svelte | 2 +- .../src/components/AttachmentActions.svelte | 25 +- .../src/components/AttachmentDocList.svelte | 11 +- .../AttachmentGalleryPresenter.svelte | 21 +- .../components/AttachmentImagePreview.svelte | 17 +- .../src/components/AttachmentList.svelte | 4 +- .../src/components/AttachmentPopup.svelte | 12 +- .../src/components/AttachmentPresenter.svelte | 58 +-- .../src/components/AttachmentPreview.svelte | 5 +- .../AttachmentStyleBoxCollabEditor.svelte | 4 +- .../src/components/AttachmentStyledBox.svelte | 4 +- .../components/AttachmentVideoPreview.svelte | 7 +- .../components/AttachmentsGalleryView.svelte | 15 +- .../src/components/AttachmentsListView.svelte | 8 +- .../src/components/AudioPlayer.svelte | 9 +- .../src/components/MediaViewer.svelte | 112 ++++++ .../src/components/Photos.svelte | 32 +- plugins/attachment-resources/src/utils.ts | 4 +- plugins/attachment/src/index.ts | 6 +- plugins/bitrix/src/sync.ts | 12 +- plugins/bitrix/src/utils.ts | 5 +- .../src/components/PersonsPresenter.svelte | 2 +- .../src/components/DirectIcon.svelte | 4 +- .../EditChannelDescriptionAttachments.svelte | 11 +- .../src/components/Avatar.svelte | 54 +-- .../src/components/AvatarInstance.svelte | 4 +- .../src/components/AvatarRef.svelte | 51 +++ .../components/CollaborationUserAvatar.svelte | 2 +- .../src/components/CombineAvatars.svelte | 2 +- .../src/components/CreateEmployee.svelte | 12 +- .../src/components/CreateGuest.svelte | 5 +- .../src/components/CreatePerson.svelte | 17 +- .../src/components/EditEmployee.svelte | 8 +- .../src/components/EditPerson.svelte | 6 +- .../src/components/EditableAvatar.svelte | 71 ++-- .../components/EmployeePreviewPopup.svelte | 2 +- .../src/components/MergePersons.svelte | 4 +- .../src/components/OrganizationCard.svelte | 2 +- .../src/components/PersonCard.svelte | 2 +- .../src/components/PersonElement.svelte | 2 +- .../src/components/PersonIcon.svelte | 2 +- .../src/components/SelectAvatarPopup.svelte | 113 +++--- .../src/components/UserDetails.svelte | 2 +- .../src/components/UserInfo.svelte | 2 +- plugins/contact-resources/src/index.ts | 182 +++++---- plugins/contact-resources/src/utils.ts | 35 +- plugins/contact/src/index.ts | 41 ++- plugins/contact/src/utils.ts | 46 +-- .../src/components/EditDoc.svelte | 4 +- .../src/components/FilePresenter.svelte | 2 +- plugins/drive-resources/src/index.ts | 12 +- plugins/guest-resources/src/connect.ts | 19 +- .../src/components/DepartmentCard.svelte | 2 +- .../src/components/EditDepartment.svelte | 6 +- .../components/schedule/StaffPresenter.svelte | 2 +- .../src/components/CreateCustomer.svelte | 12 +- .../src/components/EmployeeInbox.svelte | 2 +- .../src/components/Notification.svelte | 2 +- .../components/PeopleNotificationsView.svelte | 2 +- .../src/components/CandidateCard.svelte | 2 +- .../src/components/CreateCandidate.svelte | 23 +- .../src/components/KanbanCard.svelte | 2 +- .../components/review/PersonsPresenter.svelte | 2 +- .../src/components/Profile.svelte | 6 +- .../src/components/WorkspaceSetting.svelte | 37 +- .../src/components/WorkspaceSettings.svelte | 2 +- plugins/setting/src/index.ts | 4 +- .../src/components/Chat.svelte | 2 +- .../src/components/EmployeeSelector.svelte | 2 +- .../components/components/LeadPopup.svelte | 2 +- plugins/view-resources/src/blob.ts | 4 +- .../src/components/viewer/AudioPlayer.svelte | 8 +- .../src/components/viewer/AudioViewer.svelte | 7 +- .../src/components/viewer/ImageViewer.svelte | 9 +- .../src/components/viewer/PDFViewer.svelte | 7 +- .../src/components/viewer/VideoViewer.svelte | 7 +- .../src/components/AccountPopup.svelte | 2 +- .../src/components/Logo.svelte | 18 +- .../src/components/Workbench.svelte | 5 +- plugins/workbench-resources/src/connect.ts | 19 +- pods/server/src/server.ts | 6 +- server-plugins/contact-resources/src/index.ts | 17 +- .../notification-resources/src/index.ts | 19 +- server/account/src/operations.ts | 19 +- server/collaborator/src/server.ts | 5 +- server/core/src/adapter.ts | 6 + server/core/src/configuration.ts | 3 +- server/core/src/fulltext.ts | 3 +- server/core/src/index.ts | 3 +- server/core/src/indexer/content.ts | 2 +- server/core/src/indexer/field.ts | 13 +- server/core/src/indexer/fulltextPush.ts | 3 +- server/core/src/indexer/summary.ts | 5 +- server/core/src/indexer/types.ts | 4 +- server/core/src/mapper.ts | 4 +- server/core/src/pipeline.ts | 2 +- server/core/src/preview.ts | 42 +++ server/core/src/server/aggregator.ts | 24 +- server/core/src/server/index.ts | 31 +- server/core/src/server/storage.ts | 9 +- server/core/src/types.ts | 30 +- server/core/src/utils.ts | 9 +- server/front/src/index.ts | 344 ++++++++---------- server/middleware/src/base.ts | 3 +- server/middleware/src/blobs.ts | 117 ++++++ server/middleware/src/configuration.ts | 3 +- server/middleware/src/index.ts | 1 + server/middleware/src/lookup.ts | 3 +- server/middleware/src/modified.ts | 4 +- server/middleware/src/private.ts | 3 +- server/middleware/src/queryJoin.ts | 14 +- server/middleware/src/spacePermissions.ts | 3 +- server/middleware/src/spaceSecurity.ts | 3 +- server/middleware/src/utils.ts | 4 +- server/minio/package.json | 1 + server/minio/src/index.ts | 23 +- server/mongo/src/__tests__/storage.test.ts | 6 +- server/mongo/src/rawAdapter.ts | 52 +++ server/s3/package.json | 3 +- server/s3/src/index.ts | 48 ++- server/ws/src/__tests__/server.test.ts | 3 +- server/ws/src/client.ts | 12 +- .../model/tracker/common-tracker-page.ts | 2 +- 158 files changed, 1778 insertions(+), 1032 deletions(-) create mode 100644 plugins/attachment-resources/src/components/MediaViewer.svelte create mode 100644 plugins/contact-resources/src/components/AvatarRef.svelte create mode 100644 server/core/src/preview.ts create mode 100644 server/middleware/src/blobs.ts diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 0f05f67513..d389de2880 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -11,6 +11,9 @@ dependencies: '@aws-sdk/client-s3': specifier: ^3.575.0 version: 3.577.0 + '@aws-sdk/s3-request-presigner': + specifier: ^3.582.0 + version: 3.582.0 '@elastic/elasticsearch': specifier: ^7.14.0 version: 7.17.13 @@ -1857,6 +1860,21 @@ packages: tslib: 2.6.2 dev: false + /@aws-sdk/middleware-sdk-s3@3.582.0: + resolution: {integrity: sha512-PJqQpLoLaZPRI4L/XZUeHkd9UVK8VAr9R38wv0osGeMTvzD9iwzzk0I2TtBqFda/5xEB1YgVYZwyqvmStXmttg==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-sdk/types': 3.577.0 + '@aws-sdk/util-arn-parser': 3.568.0 + '@smithy/node-config-provider': 3.0.0 + '@smithy/protocol-http': 4.0.0 + '@smithy/signature-v4': 3.0.0 + '@smithy/smithy-client': 3.0.1 + '@smithy/types': 3.0.0 + '@smithy/util-config-provider': 3.0.0 + tslib: 2.6.2 + dev: false + /@aws-sdk/middleware-signing@3.577.0: resolution: {integrity: sha512-QS/dh3+NqZbXtY0j/DZ867ogP413pG5cFGqBy9OeOhDMsolcwLrQbi0S0c621dc1QNq+er9ffaMhZ/aPkyXXIg==} engines: {node: '>=16.0.0'} @@ -1902,6 +1920,20 @@ packages: tslib: 2.6.2 dev: false + /@aws-sdk/s3-request-presigner@3.582.0: + resolution: {integrity: sha512-h2tn0IjJ3Tsnh0Ep8FUqYwAJIjursK68gegrWEUpf7oeJlJer5gaNlD5CXCeRHwyhNiA1uzHaX4BjjyeKHl0Kw==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-sdk/signature-v4-multi-region': 3.582.0 + '@aws-sdk/types': 3.577.0 + '@aws-sdk/util-format-url': 3.577.0 + '@smithy/middleware-endpoint': 3.0.0 + '@smithy/protocol-http': 4.0.0 + '@smithy/smithy-client': 3.0.1 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + dev: false + /@aws-sdk/signature-v4-multi-region@3.577.0: resolution: {integrity: sha512-mMykGRFBYmlDcMhdbhNM0z1JFUaYYZ8r9WV7Dd0T2PWELv2brSAjDAOBHdJLHObDMYRnM6H0/Y974qTl3icEcQ==} engines: {node: '>=16.0.0'} @@ -1914,6 +1946,18 @@ packages: tslib: 2.6.2 dev: false + /@aws-sdk/signature-v4-multi-region@3.582.0: + resolution: {integrity: sha512-aFCOjjNqEX2l+V8QjOWy5F7CtHIC/RlYdBuv3No6yxn+pMvVUUe6zdMk2yHWcudVpHWsyvcZzAUBliAPeFLPsQ==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-sdk/middleware-sdk-s3': 3.582.0 + '@aws-sdk/types': 3.577.0 + '@smithy/protocol-http': 4.0.0 + '@smithy/signature-v4': 3.0.0 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + dev: false + /@aws-sdk/token-providers@3.577.0(@aws-sdk/client-sso-oidc@3.577.0): resolution: {integrity: sha512-0CkIZpcC3DNQJQ1hDjm2bdSy/Xjs7Ny5YvSsacasGOkNfk+FdkiQy6N67bZX3Zbc9KIx+Nz4bu3iDeNSNplnnQ==} engines: {node: '>=16.0.0'} @@ -1953,6 +1997,16 @@ packages: tslib: 2.6.2 dev: false + /@aws-sdk/util-format-url@3.577.0: + resolution: {integrity: sha512-SyEGC2J+y/krFRuPgiF02FmMYhqbiIkOjDE6k4nYLJQRyS6XEAGxZoG+OHeOVEM+bsDgbxokXZiM3XKGu6qFIg==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-sdk/types': 3.577.0 + '@smithy/querystring-builder': 3.0.0 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + dev: false + /@aws-sdk/util-locate-window@3.568.0: resolution: {integrity: sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig==} engines: {node: '>=16.0.0'} @@ -19662,7 +19716,7 @@ packages: dev: false file:projects/drive-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2): - resolution: {integrity: sha512-E2zO+OiX83Ig7B8ZJHiKJZydZf0RqPxXrHBnGl99l3biAcxNId4UJryMUM8KC7PQQw16ue/bERRU1dOBIs9Hng==, tarball: file:projects/drive-resources.tgz} + resolution: {integrity: sha512-BxbFyIUUuLwK5yzY3OTgPRmKE1fZxRUW9ZJ3iC/7nxvu5b32end9aJqm2fwZwNQZJxxeyw1AMbf9t7EDiadfMg==, tarball: file:projects/drive-resources.tgz} id: file:projects/drive-resources.tgz name: '@rush-temp/drive-resources' version: 0.0.0 @@ -20629,7 +20683,7 @@ packages: dev: false file:projects/minio.tgz(esbuild@0.20.1)(ts-node@10.9.2): - resolution: {integrity: sha512-9iaGGSGj2mgvQwhe6oLTf5RzaROFLBsi+kU/nMGXxw3m7pvmELcgVW2Ijk4R+P/AHBZF5TDp0jWdFif8WWvwXg==, tarball: file:projects/minio.tgz} + resolution: {integrity: sha512-wjtXX+XX515IIjugAtzTrJN8TyMp3iPEgBlQBXvxMuOpIBEmLQLnuOOD4LN7S9sdKYLXOP0IgmDVwyDp0bZvyw==, tarball: file:projects/minio.tgz} id: file:projects/minio.tgz name: '@rush-temp/minio' version: 0.0.0 @@ -22817,12 +22871,13 @@ packages: dev: false file:projects/s3.tgz(esbuild@0.20.1)(ts-node@10.9.2): - resolution: {integrity: sha512-2gkJlE5D6k8qqIbcjFSxW2ytwlziBej2ZJz7KRaEiAU5uDaMCiL378VaVvS1DmvPvPxmHJ7swZ5IkL/WRqxwqg==, tarball: file:projects/s3.tgz} + resolution: {integrity: sha512-545xE/hFO0K5PpSsSnM7hTqQilfo1Psno9mMs6XSYEC8SZQFX/mV1j0/W9l3++yX7lZQwxHKHBv/morA8v/RAA==, tarball: file:projects/s3.tgz} id: file:projects/s3.tgz name: '@rush-temp/s3' version: 0.0.0 dependencies: '@aws-sdk/client-s3': 3.577.0 + '@aws-sdk/s3-request-presigner': 3.582.0 '@types/jest': 29.5.12 '@types/node': 20.11.19 '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3) diff --git a/dev/tool/src/clean.ts b/dev/tool/src/clean.ts index f07c4aa0d2..cf3bb399fa 100644 --- a/dev/tool/src/clean.ts +++ b/dev/tool/src/clean.ts @@ -17,42 +17,42 @@ import attachment from '@hcengineering/attachment' import chunter, { type ChatMessage } from '@hcengineering/chunter' import contact from '@hcengineering/contact' import core, { + ClassifierKind, + DOMAIN_STATUS, DOMAIN_TX, - type MeasureContext, SortingOrder, TxOperations, TxProcessor, generateId, getObjectValue, + toIdMap, type BackupClient, + type Class, type Client as CoreClient, type Doc, type Domain, + type MeasureContext, type Ref, - type TxCreateDoc, - type WorkspaceId, + type Status, type StatusCategory, - type TxMixin, type TxCUD, + type TxCreateDoc, + type TxMixin, type TxUpdateDoc, - DOMAIN_STATUS, - type Status, - toIdMap, - type Class, - ClassifierKind + type WorkspaceId } from '@hcengineering/core' +import { DOMAIN_ACTIVITY } from '@hcengineering/model-activity' +import { DOMAIN_SPACE } from '@hcengineering/model-core' +import recruitModel, { defaultApplicantStatuses } from '@hcengineering/model-recruit' import { getWorkspaceDB } from '@hcengineering/mongo' import recruit, { type Applicant, type Vacancy } from '@hcengineering/recruit' -import recruitModel, { defaultApplicantStatuses } from '@hcengineering/model-recruit' import { type StorageAdapter } from '@hcengineering/server-core' import { connect } from '@hcengineering/server-tool' import tags, { type TagCategory, type TagElement, type TagReference } from '@hcengineering/tags' -import task, { type Task, type ProjectType, type TaskType } from '@hcengineering/task' +import task, { type ProjectType, type Task, type TaskType } from '@hcengineering/task' import tracker from '@hcengineering/tracker' import { deepEqual } from 'fast-equals' import { MongoClient } from 'mongodb' -import { DOMAIN_ACTIVITY } from '@hcengineering/model-activity' -import { DOMAIN_SPACE } from '@hcengineering/model-core' export async function cleanWorkspace ( ctx: MeasureContext, @@ -77,7 +77,7 @@ export async function cleanWorkspace ( const contacts = await ops.findAll(contact.class.Contact, {}) const files = new Set( - attachments.map((it) => it.file).concat(contacts.map((it) => it.avatar).filter((it) => it) as string[]) + attachments.map((it) => it.file as string).concat(contacts.map((it) => it.avatar).filter((it) => it) as string[]) ) const minioList = await storageAdapter.listStream(ctx, workspaceId) @@ -177,7 +177,7 @@ export async function fixMinioBW ( break } if (obj.modifiedOn < from) continue - if ((obj._id as string).includes('%size%')) { + if ((obj._id as string).includes('%preview%')) { await storageService.remove(ctx, workspaceId, [obj._id]) removed++ if (removed % 100 === 0) { diff --git a/models/attachment/src/index.ts b/models/attachment/src/index.ts index 89e3e59da8..df951c8ee5 100644 --- a/models/attachment/src/index.ts +++ b/models/attachment/src/index.ts @@ -15,18 +15,18 @@ import activity from '@hcengineering/activity' import type { Attachment, AttachmentMetadata, Photo, SavedAttachments } from '@hcengineering/attachment' -import { type Domain, IndexKind, type Ref } from '@hcengineering/core' +import { IndexKind, type Blob, type Domain, type Ref } from '@hcengineering/core' import { - type Builder, Index, Model, Prop, - TypeAttachment, + TypeBlob, TypeBoolean, TypeRef, TypeString, TypeTimestamp, - UX + UX, + type Builder } from '@hcengineering/model' import core, { TAttachedDoc } from '@hcengineering/model-core' import preference, { TPreference } from '@hcengineering/model-preference' @@ -46,8 +46,8 @@ export class TAttachment extends TAttachedDoc implements Attachment { @Index(IndexKind.FullText) name!: string - @Prop(TypeAttachment(), attachment.string.File) - file!: string + @Prop(TypeBlob(), attachment.string.File) + file!: Ref @Prop(TypeString(), attachment.string.Size) size!: number diff --git a/models/contact/src/index.ts b/models/contact/src/index.ts index 3cb79241ee..b29dc517da 100644 --- a/models/contact/src/index.ts +++ b/models/contact/src/index.ts @@ -36,6 +36,7 @@ import { DOMAIN_MODEL, DateRangeMode, IndexKind, + type Blob, type Class, type Domain, type Markup, @@ -50,10 +51,11 @@ import { Model, Prop, ReadOnly, - TypeAttachment, + TypeBlob, TypeBoolean, TypeCollaborativeMarkup, TypeDate, + TypeRecord, TypeRef, TypeString, TypeTimestamp, @@ -104,10 +106,23 @@ export class TContact extends TDoc implements Contact { @Index(IndexKind.FullText) name!: string - @Prop(TypeAttachment(), contact.string.Avatar) + @Prop(TypeString(), contact.string.Avatar) @Index(IndexKind.FullText) @Hidden() - avatar?: string | null + avatarType!: AvatarType + + @Prop(TypeBlob(), contact.string.Avatar) + @Index(IndexKind.FullText) + @Hidden() + avatar!: Ref | null | undefined + + @Prop(TypeRecord(), contact.string.Avatar) + @Index(IndexKind.FullText) + @Hidden() + avatarProps?: { + color?: string + url?: string + } @Prop(Collection(contact.class.Channel), contact.string.ContactInfo) channels?: number @@ -709,6 +724,16 @@ export function createModel (builder: Builder): void { contact.avatarProvider.Gravatar ) + builder.createDoc( + contact.class.AvatarProvider, + core.space.Model, + { + type: AvatarType.EXTERNAL, + getUrl: contact.function.GetExternalUrl + }, + contact.avatarProvider.Color + ) + builder.mixin(contact.class.Person, core.class.Class, view.mixin.ObjectPresenter, { presenter: contact.component.PersonPresenter }) diff --git a/models/contact/src/migration.ts b/models/contact/src/migration.ts index 712cde4e08..98b81dfcb1 100644 --- a/models/contact/src/migration.ts +++ b/models/contact/src/migration.ts @@ -1,12 +1,14 @@ // -import { DOMAIN_TX, type Space, TxOperations, type Class, type Doc, type Domain, type Ref } from '@hcengineering/core' +import { DOMAIN_TX, TxOperations, type Class, type Doc, type Domain, type Ref, type Space } from '@hcengineering/core' import { createDefaultSpace, tryMigrate, tryUpgrade, type MigrateOperation, + type MigrateUpdate, type MigrationClient, + type MigrationDocumentQuery, type MigrationUpgradeClient, type ModelLogger } from '@hcengineering/model' @@ -14,6 +16,7 @@ import activity, { DOMAIN_ACTIVITY } from '@hcengineering/model-activity' import core from '@hcengineering/model-core' import { DOMAIN_VIEW } from '@hcengineering/model-view' +import { AvatarType, type Contact } from '@hcengineering/contact' import contact, { DOMAIN_CONTACT, contactId } from './index' async function createEmployeeEmail (client: TxOperations): Promise { @@ -48,6 +51,55 @@ async function createEmployeeEmail (client: TxOperations): Promise { } } +const colorPrefix = 'color://' +const gravatarPrefix = 'gravatar://' + +async function migrateAvatars (client: MigrationClient): Promise { + const classes = client.hierarchy.getDescendants(contact.class.Contact) + const i = await client.traverse(DOMAIN_CONTACT, { + _class: { $in: classes }, + avatar: { $regex: 'color|gravatar://.*' } + }) + while (true) { + const docs = await i.next(50) + if (docs === null || docs?.length === 0) { + break + } + const updates: { filter: MigrationDocumentQuery, update: MigrateUpdate }[] = [] + for (const d of docs) { + if (d.avatar?.startsWith(colorPrefix) ?? false) { + d.avatarProps = { color: d.avatar?.slice(colorPrefix.length) ?? '' } + updates.push({ + filter: { _id: d._id }, + update: { + avatarType: AvatarType.COLOR, + avatar: null, + avatarProps: { color: d.avatar?.slice(colorPrefix.length) ?? '' } + } + }) + } else if (d.avatar?.startsWith(gravatarPrefix) ?? false) { + updates.push({ + filter: { _id: d._id }, + update: { + avatarType: AvatarType.GRAVATAR, + avatar: null, + avatarProps: { url: d.avatar?.slice(gravatarPrefix.length) ?? '' } + } + }) + } + } + if (updates.length > 0) { + await client.bulk(DOMAIN_CONTACT, updates) + } + } + + await client.update( + DOMAIN_CONTACT, + { _class: { $in: classes }, avatarKind: { $exists: false } }, + { avatarKind: AvatarType.IMAGE } + ) +} + export const contactOperation: MigrateOperation = { async migrate (client: MigrationClient, logger: ModelLogger): Promise { await tryMigrate(client, contactId, [ @@ -183,6 +235,12 @@ export const contactOperation: MigrateOperation = { } ) } + }, + { + state: 'avatars', + func: async (client) => { + await migrateAvatars(client) + } } ]) }, diff --git a/models/core/src/core.ts b/models/core/src/core.ts index 8b67882744..317a7be341 100644 --- a/models/core/src/core.ts +++ b/models/core/src/core.ts @@ -213,8 +213,8 @@ export class TTypeString extends TType {} export class TTypeRecord extends TType {} @UX(core.string.String) -@Model(core.class.TypeAttachment, core.class.Type) -export class TTypeAttachment extends TType {} +@Model(core.class.TypeBlob, core.class.Type) +export class TTypeBlob extends TType {} @UX(core.string.Hyperlink) @Model(core.class.TypeHyperlink, core.class.Type) diff --git a/models/core/src/index.ts b/models/core/src/index.ts index 480fc5e874..2d0105c52e 100644 --- a/models/core/src/index.ts +++ b/models/core/src/index.ts @@ -60,7 +60,7 @@ import { TRefTo, TType, TTypeAny, - TTypeAttachment, + TTypeBlob, TTypeBoolean, TTypeCollaborativeDoc, TTypeCollaborativeDocVersion, @@ -153,7 +153,7 @@ export function createModel (builder: Builder): void { TTypeString, TTypeRank, TTypeRecord, - TTypeAttachment, + TTypeBlob, TTypeHyperlink, TCollection, TVersion, diff --git a/models/presentation/src/index.ts b/models/presentation/src/index.ts index 9b900f2461..c047798f6f 100644 --- a/models/presentation/src/index.ts +++ b/models/presentation/src/index.ts @@ -13,8 +13,8 @@ // limitations under the License. // -import { type Blob, type Class, type Doc, type Ref, DOMAIN_MODEL } from '@hcengineering/core' -import { type Builder, Model, Prop, TypeRef, TypeString } from '@hcengineering/model' +import { DOMAIN_MODEL, type Blob, type Class, type Doc, type Ref } from '@hcengineering/core' +import { Model, Prop, TypeRef, TypeString, type Builder } from '@hcengineering/model' import core, { TDoc } from '@hcengineering/model-core' import { type Asset, type IntlString, type Resource } from '@hcengineering/platform' // Import types to prevent .svelte components to being exposed to type typescript. @@ -27,12 +27,12 @@ import { type ComponentPointExtension, type CreateExtensionKind, type DocAttributeRule, - type DocRules, type DocCreateExtension, type DocCreateFunction, + type DocRules, type FilePreviewExtension, - type ObjectSearchContext, type ObjectSearchCategory, + type ObjectSearchContext, type ObjectSearchFactory } from '@hcengineering/presentation/src/types' import { type AnyComponent, type ComponentExtensionId } from '@hcengineering/ui/src/types' diff --git a/models/server-contact/src/index.ts b/models/server-contact/src/index.ts index 9ccf0b083e..6e822e8128 100644 --- a/models/server-contact/src/index.ts +++ b/models/server-contact/src/index.ts @@ -45,8 +45,8 @@ export function createModel (builder: Builder): void { builder.mixin(contact.class.Contact, core.class.Class, serverCore.mixin.SearchPresenter, { searchConfig: { iconConfig: { - component: contact.component.Avatar, - props: ['avatar', 'name'] + component: contact.component.AvatarRef, + props: ['_id'] }, title: { props: ['name'] } }, diff --git a/models/server-recruit/src/index.ts b/models/server-recruit/src/index.ts index c265f344ec..c33b390368 100644 --- a/models/server-recruit/src/index.ts +++ b/models/server-recruit/src/index.ts @@ -57,8 +57,8 @@ export function createModel (builder: Builder): void { builder.mixin(recruit.class.Applicant, core.class.Class, serverCore.mixin.SearchPresenter, { searchConfig: { iconConfig: { - component: contact.component.Avatar, - props: [{ avatar: ['attachedTo', 'avatar'] }, { name: ['attachedTo', 'name'] }] + component: contact.component.AvatarRef, + props: [{ _id: ['attachedTo'] }] }, shortTitle: 'identifier', title: { diff --git a/models/setting/src/index.ts b/models/setting/src/index.ts index ae280fc866..b475427a71 100644 --- a/models/setting/src/index.ts +++ b/models/setting/src/index.ts @@ -15,7 +15,7 @@ import activity from '@hcengineering/activity' import contact from '@hcengineering/contact' -import { AccountRole, DOMAIN_MODEL, type Account, type Domain, type Ref } from '@hcengineering/core' +import { AccountRole, DOMAIN_MODEL, type Account, type Blob, type Domain, type Ref } from '@hcengineering/core' import { Mixin, Model, type Builder, UX } from '@hcengineering/model' import core, { TClass, TConfiguration, TDoc } from '@hcengineering/model-core' import view, { createAction } from '@hcengineering/model-view' @@ -105,7 +105,7 @@ export class TInviteSettings extends TConfiguration implements InviteSettings { @Model(setting.class.WorkspaceSetting, core.class.Doc, DOMAIN_SETTING) export class TWorkspaceSetting extends TDoc implements WorkspaceSetting { - icon?: string + icon?: Ref } @Mixin(setting.mixin.SpaceTypeEditor, core.class.Class) diff --git a/models/view/src/index.ts b/models/view/src/index.ts index 3852b2d3b6..c939c22683 100644 --- a/models/view/src/index.ts +++ b/models/view/src/index.ts @@ -460,7 +460,7 @@ export function createModel (builder: Builder): void { view.component.StringEditor, view.component.StringEditorPopup ) - classPresenter(builder, core.class.TypeAttachment, view.component.StringPresenter) + classPresenter(builder, core.class.TypeBlob, view.component.StringPresenter) classPresenter( builder, core.class.TypeHyperlink, diff --git a/packages/core/src/classes.ts b/packages/core/src/classes.ts index ddb291b59d..92b4faf5f1 100644 --- a/packages/core/src/classes.ts +++ b/packages/core/src/classes.ts @@ -559,6 +559,22 @@ export interface Blob extends Doc { size: number } +/** + * For every blob will automatically add a lookup. + * + * It extends Blob to allow for $lookup operations work as expected. + */ +export interface BlobLookup extends Blob { + // An URL document could be downloaded from, with ${id} to put blobId into + downloadUrl: string + // A URL document could be updated at + uploadUrl?: string + // A URL document could be previewed at + previewUrl?: string + // A formats preview is available at + previewFormats?: string[] +} + /** * @public * diff --git a/packages/core/src/component.ts b/packages/core/src/component.ts index d17d388582..c793529910 100644 --- a/packages/core/src/component.ts +++ b/packages/core/src/component.ts @@ -111,7 +111,7 @@ export default plugin(coreId, { Account: '' as Ref>, Type: '' as Ref>>, TypeString: '' as Ref>>, - TypeAttachment: '' as Ref>>, + TypeBlob: '' as Ref>>>, TypeIntlString: '' as Ref>>, TypeHyperlink: '' as Ref>>, TypeNumber: '' as Ref>>, diff --git a/packages/core/src/server.ts b/packages/core/src/server.ts index 20fd3c7425..7bfe34d563 100644 --- a/packages/core/src/server.ts +++ b/packages/core/src/server.ts @@ -13,20 +13,8 @@ // limitations under the License. // -import { LoadModelResponse } from '.' -import type { Class, Doc, Domain, Ref, Timestamp } from './classes' -import { Hierarchy } from './hierarchy' +import type { Doc, Domain, Ref } from './classes' import { MeasureContext, type FullParamsType, type ParamsType } from './measurements' -import { ModelDb } from './memdb' -import type { - DocumentQuery, - FindOptions, - FindResult, - SearchOptions, - SearchQuery, - SearchResult, - TxResult -} from './storage' import type { Tx } from './tx' /** @@ -78,24 +66,3 @@ export interface LowLevelStorage { // Remove a list of documents. clean: (ctx: MeasureContext, domain: Domain, docs: Ref[]) => Promise } -/** - * @public - */ -export interface ServerStorage extends LowLevelStorage { - hierarchy: Hierarchy - modelDb: ModelDb - findAll: ( - ctx: MeasureContext, - _class: Ref>, - query: DocumentQuery, - options?: FindOptions & { - domain?: Domain // Allow to find for Doc's in specified domain only. - prefix?: string - } - ) => Promise> - searchFulltext: (ctx: MeasureContext, query: SearchQuery, options: SearchOptions) => Promise - tx: (ctx: SessionOperationContext, tx: Tx) => Promise - apply: (ctx: SessionOperationContext, tx: Tx[], broadcast: boolean) => Promise - close: () => Promise - loadModel: (last: Timestamp, hash?: string) => Promise -} diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 8793c37d54..a774dec680 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -214,7 +214,7 @@ export function extractDocKey (key: string): { export function isFullTextAttribute (attr: AnyAttribute): boolean { return ( attr.index === IndexKind.FullText || - attr.type._class === core.class.TypeAttachment || + attr.type._class === core.class.TypeBlob || attr.type._class === core.class.EnumOf || attr.type._class === core.class.TypeCollaborativeDoc ) diff --git a/packages/model/src/dsl.ts b/packages/model/src/dsl.ts index fee18a702c..e81a867a66 100644 --- a/packages/model/src/dsl.ts +++ b/packages/model/src/dsl.ts @@ -387,8 +387,8 @@ export function TypeString (): Type { /** * @public */ -export function TypeAttachment (): Type { - return { _class: core.class.TypeAttachment, label: core.string.String } +export function TypeBlob (): Type { + return { _class: core.class.TypeBlob, label: core.string.String } } /** diff --git a/packages/presentation/src/components/FilePreviewPopup.svelte b/packages/presentation/src/components/FilePreviewPopup.svelte index e6e6e2b82f..c0f785bb9f 100644 --- a/packages/presentation/src/components/FilePreviewPopup.svelte +++ b/packages/presentation/src/components/FilePreviewPopup.svelte @@ -13,20 +13,20 @@ // limitations under the License. --> diff --git a/packages/presentation/src/components/PDFViewer.svelte b/packages/presentation/src/components/PDFViewer.svelte index 9fc83270e7..5668ded6dd 100644 --- a/packages/presentation/src/components/PDFViewer.svelte +++ b/packages/presentation/src/components/PDFViewer.svelte @@ -14,14 +14,15 @@ -->