mirror of
https://github.com/toeverything/AFFiNE.git
synced 2025-01-05 07:27:17 +03:00
fix(core): transform workspace db when enable cloud (#7744)
This commit is contained in:
parent
a03831f2a2
commit
cd4e462d8c
@ -6,6 +6,7 @@ import { WorkspaceDBService } from './services/db';
|
||||
|
||||
export { AFFiNE_WORKSPACE_DB_SCHEMA } from './schema';
|
||||
export { WorkspaceDBService } from './services/db';
|
||||
export { transformWorkspaceDBLocalToCloud } from './services/db';
|
||||
|
||||
export function configureWorkspaceDBModule(framework: Framework) {
|
||||
framework
|
||||
|
@ -2,6 +2,7 @@ import { Doc as YDoc } from 'yjs';
|
||||
|
||||
import { Service } from '../../../framework';
|
||||
import { createORMClient, YjsDBAdapter } from '../../../orm';
|
||||
import type { DocStorage } from '../../../sync';
|
||||
import { ObjectPool } from '../../../utils';
|
||||
import type { WorkspaceService } from '../../workspace';
|
||||
import { DB, type DBWithTables } from '../entities/db';
|
||||
@ -90,3 +91,29 @@ export class WorkspaceDBService extends Service {
|
||||
return docId.startsWith('db$') || docId.startsWith('userdata$');
|
||||
}
|
||||
}
|
||||
|
||||
export async function transformWorkspaceDBLocalToCloud(
|
||||
localWorkspaceId: string,
|
||||
cloudWorkspaceId: string,
|
||||
localDocStorage: DocStorage,
|
||||
cloudDocStorage: DocStorage,
|
||||
accountId: string
|
||||
) {
|
||||
for (const tableName of Object.keys(AFFiNE_WORKSPACE_DB_SCHEMA)) {
|
||||
const localDocName = `db$${localWorkspaceId}$${tableName}`;
|
||||
const localDoc = await localDocStorage.doc.get(localDocName);
|
||||
if (localDoc) {
|
||||
const cloudDocName = `db$${cloudWorkspaceId}$${tableName}`;
|
||||
await cloudDocStorage.doc.set(cloudDocName, localDoc);
|
||||
}
|
||||
}
|
||||
|
||||
for (const tableName of Object.keys(AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA)) {
|
||||
const localDocName = `userdata$__local__$${localWorkspaceId}$${tableName}`;
|
||||
const localDoc = await localDocStorage.doc.get(localDocName);
|
||||
if (localDoc) {
|
||||
const cloudDocName = `userdata$${accountId}$${cloudWorkspaceId}$${tableName}`;
|
||||
await cloudDocStorage.doc.set(cloudDocName, localDoc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,8 @@ export interface WorkspaceFlavourProvider {
|
||||
createWorkspace(
|
||||
initial: (
|
||||
docCollection: DocCollection,
|
||||
blobStorage: BlobStorage
|
||||
blobStorage: BlobStorage,
|
||||
docStorage: DocStorage
|
||||
) => Promise<void>
|
||||
): Promise<WorkspaceMetadata>;
|
||||
|
||||
|
@ -2,7 +2,7 @@ import type { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import type { DocCollection } from '@blocksuite/store';
|
||||
|
||||
import { Service } from '../../../framework';
|
||||
import type { BlobStorage } from '../../../sync';
|
||||
import type { BlobStorage, DocStorage } from '../../../sync';
|
||||
import type { WorkspaceFlavourProvider } from '../providers/flavour';
|
||||
|
||||
export class WorkspaceFactoryService extends Service {
|
||||
@ -20,7 +20,8 @@ export class WorkspaceFactoryService extends Service {
|
||||
flavour: WorkspaceFlavour,
|
||||
initial: (
|
||||
docCollection: DocCollection,
|
||||
blobStorage: BlobStorage
|
||||
blobStorage: BlobStorage,
|
||||
docStorage: DocStorage
|
||||
) => Promise<void> = () => Promise.resolve()
|
||||
) => {
|
||||
const provider = this.providers.find(x => x.flavour === flavour);
|
||||
|
@ -3,6 +3,7 @@ import { assertEquals } from '@blocksuite/global/utils';
|
||||
import { applyUpdate, encodeStateAsUpdate } from 'yjs';
|
||||
|
||||
import { Service } from '../../../framework';
|
||||
import { transformWorkspaceDBLocalToCloud } from '../../db';
|
||||
import type { Workspace } from '../entities/workspace';
|
||||
import type { WorkspaceMetadata } from '../metadata';
|
||||
import type { WorkspaceDestroyService } from './destroy';
|
||||
@ -18,9 +19,12 @@ export class WorkspaceTransformService extends Service {
|
||||
|
||||
/**
|
||||
* helper function to transform local workspace to cloud workspace
|
||||
*
|
||||
* @param accountId - all local user data will be transformed to this account
|
||||
*/
|
||||
transformLocalToCloud = async (
|
||||
local: Workspace
|
||||
local: Workspace,
|
||||
accountId: string
|
||||
): Promise<WorkspaceMetadata> => {
|
||||
assertEquals(local.flavour, WorkspaceFlavour.LOCAL);
|
||||
|
||||
@ -28,23 +32,35 @@ export class WorkspaceTransformService extends Service {
|
||||
|
||||
const newMetadata = await this.factory.create(
|
||||
WorkspaceFlavour.AFFINE_CLOUD,
|
||||
async (ws, bs) => {
|
||||
applyUpdate(ws.doc, encodeStateAsUpdate(local.docCollection.doc));
|
||||
async (docCollection, blobStorage, docStorage) => {
|
||||
applyUpdate(
|
||||
docCollection.doc,
|
||||
encodeStateAsUpdate(local.docCollection.doc)
|
||||
);
|
||||
|
||||
for (const subdoc of local.docCollection.doc.getSubdocs()) {
|
||||
for (const newSubdoc of ws.doc.getSubdocs()) {
|
||||
for (const newSubdoc of docCollection.doc.getSubdocs()) {
|
||||
if (newSubdoc.guid === subdoc.guid) {
|
||||
applyUpdate(newSubdoc, encodeStateAsUpdate(subdoc));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// transform db
|
||||
await transformWorkspaceDBLocalToCloud(
|
||||
local.id,
|
||||
docCollection.id,
|
||||
local.engine.doc.storage.behavior,
|
||||
docStorage,
|
||||
accountId
|
||||
);
|
||||
|
||||
const blobList = await local.engine.blob.list();
|
||||
|
||||
for (const blobKey of blobList) {
|
||||
const blob = await local.engine.blob.get(blobKey);
|
||||
if (blob) {
|
||||
await bs.set(blobKey, blob);
|
||||
await blobStorage.set(blobKey, blob);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,11 @@ import { applyUpdate, encodeStateAsUpdate } from 'yjs';
|
||||
import { Service } from '../../../framework';
|
||||
import { LiveData } from '../../../livedata';
|
||||
import { wrapMemento } from '../../../storage';
|
||||
import { type BlobStorage, MemoryDocStorage } from '../../../sync';
|
||||
import {
|
||||
type BlobStorage,
|
||||
type DocStorage,
|
||||
MemoryDocStorage,
|
||||
} from '../../../sync';
|
||||
import { MemoryBlobStorage } from '../../../sync/blob/blob';
|
||||
import type { GlobalState } from '../../storage';
|
||||
import type { WorkspaceProfileInfo } from '../entities/profile';
|
||||
@ -39,7 +43,8 @@ export class TestingWorkspaceLocalProvider
|
||||
async createWorkspace(
|
||||
initial: (
|
||||
docCollection: DocCollection,
|
||||
blobStorage: BlobStorage
|
||||
blobStorage: BlobStorage,
|
||||
docStorage: DocStorage
|
||||
) => Promise<void>
|
||||
): Promise<WorkspaceMetadata> {
|
||||
const id = nanoid();
|
||||
@ -59,7 +64,7 @@ export class TestingWorkspaceLocalProvider
|
||||
});
|
||||
|
||||
// apply initial state
|
||||
await initial(docCollection, blobStorage);
|
||||
await initial(docCollection, blobStorage, this.docStorage);
|
||||
|
||||
// save workspace to storage
|
||||
await this.docStorage.doc.set(id, encodeStateAsUpdate(docCollection.doc));
|
||||
|
@ -29,6 +29,8 @@ type ConfirmEnableArgs = [Workspace, ConfirmEnableCloudOptions | undefined];
|
||||
|
||||
export const useEnableCloud = () => {
|
||||
const t = useI18n();
|
||||
const authService = useService(AuthService);
|
||||
const account = useLiveData(authService.session.account$);
|
||||
const loginStatus = useLiveData(useService(AuthService).session.status$);
|
||||
const setAuthAtom = useSetAtom(authAtom);
|
||||
const { openConfirmModal, closeConfirmModal } = useConfirmModal();
|
||||
@ -39,7 +41,11 @@ export const useEnableCloud = () => {
|
||||
async (ws: Workspace | null, options?: ConfirmEnableCloudOptions) => {
|
||||
try {
|
||||
if (!ws) return;
|
||||
const { id: newId } = await workspacesService.transformLocalToCloud(ws);
|
||||
if (!account) return;
|
||||
const { id: newId } = await workspacesService.transformLocalToCloud(
|
||||
ws,
|
||||
account.id
|
||||
);
|
||||
openPage(newId, options?.openPageId || WorkspaceSubPath.ALL);
|
||||
options?.onSuccess?.();
|
||||
} catch (e) {
|
||||
@ -49,7 +55,7 @@ export const useEnableCloud = () => {
|
||||
});
|
||||
}
|
||||
},
|
||||
[openPage, t, workspacesService]
|
||||
[account, openPage, t, workspacesService]
|
||||
);
|
||||
|
||||
const openSignIn = useCallback(() => {
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
ApplicationStarted,
|
||||
type BlobStorage,
|
||||
catchErrorInto,
|
||||
type DocStorage,
|
||||
exhaustMapSwitchUntilChanged,
|
||||
fromPromise,
|
||||
type GlobalState,
|
||||
@ -77,11 +78,10 @@ export class CloudWorkspaceFlavourProviderService
|
||||
async createWorkspace(
|
||||
initial: (
|
||||
docCollection: DocCollection,
|
||||
blobStorage: BlobStorage
|
||||
blobStorage: BlobStorage,
|
||||
docStorage: DocStorage
|
||||
) => Promise<void>
|
||||
): Promise<WorkspaceMetadata> {
|
||||
const tempId = nanoid();
|
||||
|
||||
// create workspace on cloud, get workspace id
|
||||
const {
|
||||
createWorkspace: { id: workspaceId },
|
||||
@ -94,7 +94,7 @@ export class CloudWorkspaceFlavourProviderService
|
||||
const docStorage = this.storageProvider.getDocStorage(workspaceId);
|
||||
|
||||
const docCollection = new DocCollection({
|
||||
id: tempId,
|
||||
id: workspaceId,
|
||||
idGenerator: () => nanoid(),
|
||||
schema: globalBlockSuiteSchema,
|
||||
blobSources: {
|
||||
@ -103,7 +103,7 @@ export class CloudWorkspaceFlavourProviderService
|
||||
});
|
||||
|
||||
// apply initial state
|
||||
await initial(docCollection, blobStorage);
|
||||
await initial(docCollection, blobStorage, docStorage);
|
||||
|
||||
// save workspace to local storage, should be vary fast
|
||||
await docStorage.doc.set(
|
||||
|
@ -3,6 +3,7 @@ import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { DocCollection } from '@blocksuite/store';
|
||||
import type {
|
||||
BlobStorage,
|
||||
DocStorage,
|
||||
WorkspaceEngineProvider,
|
||||
WorkspaceFlavourProvider,
|
||||
WorkspaceMetadata,
|
||||
@ -56,7 +57,8 @@ export class LocalWorkspaceFlavourProvider
|
||||
async createWorkspace(
|
||||
initial: (
|
||||
docCollection: DocCollection,
|
||||
blobStorage: BlobStorage
|
||||
blobStorage: BlobStorage,
|
||||
docStorage: DocStorage
|
||||
) => Promise<void>
|
||||
): Promise<WorkspaceMetadata> {
|
||||
const id = nanoid();
|
||||
@ -73,7 +75,7 @@ export class LocalWorkspaceFlavourProvider
|
||||
});
|
||||
|
||||
// apply initial state
|
||||
await initial(docCollection, blobStorage);
|
||||
await initial(docCollection, blobStorage, docStorage);
|
||||
|
||||
// save workspace to local storage, should be vary fast
|
||||
await docStorage.doc.set(id, encodeStateAsUpdate(docCollection.doc));
|
||||
|
@ -5,9 +5,12 @@ import {
|
||||
enableCloudWorkspace,
|
||||
loginUser,
|
||||
} from '@affine-test/kit/utils/cloud';
|
||||
import { clickPageModeButton } from '@affine-test/kit/utils/editor';
|
||||
import {
|
||||
clickNewPageButton,
|
||||
getBlockSuiteEditorTitle,
|
||||
waitForEditorLoad,
|
||||
waitForEmptyEditor,
|
||||
} from '@affine-test/kit/utils/page-logic';
|
||||
import {
|
||||
openSettingModal,
|
||||
@ -94,3 +97,31 @@ test('should have pagination in member list', async ({ page }) => {
|
||||
await page.waitForTimeout(500);
|
||||
expect(await page.locator('[data-testid="member-item"]').count()).toBe(3);
|
||||
});
|
||||
|
||||
test('should transform local favorites data', async ({ page }) => {
|
||||
await page.reload();
|
||||
await waitForEditorLoad(page);
|
||||
await createLocalWorkspace(
|
||||
{
|
||||
name: 'test',
|
||||
},
|
||||
page
|
||||
);
|
||||
await page.getByTestId('explorer-bar-add-favorite-button').first().click();
|
||||
await clickPageModeButton(page);
|
||||
await waitForEmptyEditor(page);
|
||||
|
||||
await getBlockSuiteEditorTitle(page).fill('this is a new fav page');
|
||||
await expect(
|
||||
page
|
||||
.getByTestId('explorer-favorites')
|
||||
.locator('[draggable] >> text=this is a new fav page')
|
||||
).toBeVisible();
|
||||
|
||||
await enableCloudWorkspace(page);
|
||||
await expect(
|
||||
page
|
||||
.getByTestId('explorer-favorites')
|
||||
.locator('[draggable] >> text=this is a new fav page')
|
||||
).toBeVisible();
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user