feat: data center business apis
This commit is contained in:
115
packages/data-center/src/apis/business.ts
Normal file
115
packages/data-center/src/apis/business.ts
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import { uuidv4 } from '@blocksuite/store';
|
||||||
|
import { getDataCenter } from 'src';
|
||||||
|
import { DataCenter } from 'src/datacenter';
|
||||||
|
import { Workspace, WorkspaceMeta, WorkspaceType } from '../style';
|
||||||
|
import { token } from './token';
|
||||||
|
|
||||||
|
export class Business {
|
||||||
|
private _dc: DataCenter | undefined;
|
||||||
|
|
||||||
|
private async _getDc() {
|
||||||
|
if (!this._dc) {
|
||||||
|
this._dc = await getDataCenter();
|
||||||
|
}
|
||||||
|
return this._dc;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createWorkspace(
|
||||||
|
name: string
|
||||||
|
): Promise<Pick<Workspace, 'id' | 'name' | 'avatar' | 'type'>> {
|
||||||
|
let id = '';
|
||||||
|
let type = WorkspaceType.local;
|
||||||
|
if (token.isLogin) {
|
||||||
|
// TODO: add default avatar
|
||||||
|
const data = await this._dc?.apis.createWorkspace({ name, avatar: '' });
|
||||||
|
id = data?.id || '';
|
||||||
|
type = WorkspaceType.cloud;
|
||||||
|
this._dc?.load(id, { providerId: 'affine' });
|
||||||
|
} else {
|
||||||
|
this._dc?.load(uuidv4(), { providerId: 'local' });
|
||||||
|
}
|
||||||
|
const newWorkspaces = (await this.getWorkspaces()).find(w => w.id === id);
|
||||||
|
return {
|
||||||
|
id: newWorkspaces?.id || '',
|
||||||
|
name,
|
||||||
|
avatar: '',
|
||||||
|
type,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateWorkspaceMeta(
|
||||||
|
// id: string,
|
||||||
|
// meta: { name?: string; avatar: Partial<WorkspaceMeta> }
|
||||||
|
// ) {}
|
||||||
|
|
||||||
|
async getWorkspaces(focusUpdated?: boolean): Promise<Workspace[]> {
|
||||||
|
const dc = await this._getDc();
|
||||||
|
if (focusUpdated) {
|
||||||
|
await dc.workspacesList.refreshWorkspaceList();
|
||||||
|
}
|
||||||
|
return dc.workspacesList.getWorkspaces();
|
||||||
|
}
|
||||||
|
|
||||||
|
async onWorkspaceChange(cb: (workspaces: Workspace[]) => void) {
|
||||||
|
const dc = await this._getDc();
|
||||||
|
dc.workspacesList.on('change', cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteWorkspace(id: string) {
|
||||||
|
const dc = await this._getDc();
|
||||||
|
const workspace = dc.workspacesList.getWorkspaces().find(w => w.id === id);
|
||||||
|
if (workspace?.type === WorkspaceType.cloud) {
|
||||||
|
dc.apis.deleteWorkspace({ id });
|
||||||
|
}
|
||||||
|
dc.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async leaveWorkspace(id: string) {
|
||||||
|
const dc = await this._getDc();
|
||||||
|
const workspace = dc.workspacesList.getWorkspaces().find(w => w.id === id);
|
||||||
|
if (workspace?.type === WorkspaceType.cloud) {
|
||||||
|
dc.apis.leaveWorkspace({ id });
|
||||||
|
dc.delete(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setWorkspacePublish(id: string, isPublish: boolean): boolean {
|
||||||
|
return isPublish;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getWorkspaceById(id: string) {
|
||||||
|
const dc = await this._getDc();
|
||||||
|
const workspace = dc.workspacesList.getWorkspaces().find(w => w.id === id);
|
||||||
|
if (workspace?.type === WorkspaceType.cloud) {
|
||||||
|
return dc.load(id, { providerId: 'affine' });
|
||||||
|
} else {
|
||||||
|
return dc.load(id, { providerId: 'local' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getMembers(id: string): any {}
|
||||||
|
|
||||||
|
// inviteMember(id: string, email: string) {}
|
||||||
|
|
||||||
|
async acceptInvitation(invitingCode: string) {
|
||||||
|
const dc = await this._getDc();
|
||||||
|
dc.apis.acceptInviting({ invitingCode });
|
||||||
|
}
|
||||||
|
|
||||||
|
// getUserInfo(): any {}
|
||||||
|
|
||||||
|
async login() {
|
||||||
|
const dc = await this._getDc();
|
||||||
|
await dc.auth('affine');
|
||||||
|
}
|
||||||
|
|
||||||
|
// logout() {}
|
||||||
|
|
||||||
|
// setWorkspaceSyncType(id: string, type: 'local' | 'cloud') {}
|
||||||
|
|
||||||
|
// importWorkspace(file: File) {}
|
||||||
|
|
||||||
|
// exportWorkspace(id: string) {}
|
||||||
|
|
||||||
|
// enableWorkspaceCloud(id: string) {}
|
||||||
|
}
|
||||||
@@ -4,16 +4,24 @@ export type { Callback } from './token.js';
|
|||||||
import { getAuthorizer } from './token.js';
|
import { getAuthorizer } from './token.js';
|
||||||
import * as user from './user.js';
|
import * as user from './user.js';
|
||||||
import * as workspace from './workspace.js';
|
import * as workspace from './workspace.js';
|
||||||
|
import * as business from './business.js';
|
||||||
|
|
||||||
export type Apis = typeof user &
|
export type Apis = typeof user &
|
||||||
typeof workspace & {
|
typeof workspace & {
|
||||||
|
business: typeof business;
|
||||||
signInWithGoogle: ReturnType<typeof getAuthorizer>[0];
|
signInWithGoogle: ReturnType<typeof getAuthorizer>[0];
|
||||||
onAuthStateChanged: ReturnType<typeof getAuthorizer>[1];
|
onAuthStateChanged: ReturnType<typeof getAuthorizer>[1];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getApis = (): Apis => {
|
export const getApis = (): Apis => {
|
||||||
const [signInWithGoogle, onAuthStateChanged] = getAuthorizer();
|
const [signInWithGoogle, onAuthStateChanged] = getAuthorizer();
|
||||||
return { ...user, ...workspace, signInWithGoogle, onAuthStateChanged };
|
return {
|
||||||
|
...user,
|
||||||
|
...workspace,
|
||||||
|
business,
|
||||||
|
signInWithGoogle,
|
||||||
|
onAuthStateChanged,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type { AccessTokenMessage } from './token';
|
export type { AccessTokenMessage } from './token';
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export interface CreateWorkspaceParams {
|
|||||||
|
|
||||||
export async function createWorkspace(
|
export async function createWorkspace(
|
||||||
params: CreateWorkspaceParams
|
params: CreateWorkspaceParams
|
||||||
): Promise<void> {
|
): Promise<{ id: string }> {
|
||||||
return client.post('api/workspace', { json: params }).json();
|
return client.post('api/workspace', { json: params }).json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { getApis, Apis } from './apis/index.js';
|
|||||||
import { AffineProvider, BaseProvider } from './provider/index.js';
|
import { AffineProvider, BaseProvider } from './provider/index.js';
|
||||||
import { LocalProvider } from './provider/index.js';
|
import { LocalProvider } from './provider/index.js';
|
||||||
import { getKVConfigure } from './store.js';
|
import { getKVConfigure } from './store.js';
|
||||||
|
import { Workspaces } from './workspaces';
|
||||||
|
|
||||||
// load workspace's config
|
// load workspace's config
|
||||||
type LoadConfig = {
|
type LoadConfig = {
|
||||||
@@ -33,6 +34,7 @@ export class DataCenter {
|
|||||||
private readonly _workspaces = new Map<string, Promise<BaseProvider>>();
|
private readonly _workspaces = new Map<string, Promise<BaseProvider>>();
|
||||||
private readonly _config;
|
private readonly _config;
|
||||||
private readonly _logger;
|
private readonly _logger;
|
||||||
|
public readonly workspacesList = new Workspaces(this);
|
||||||
|
|
||||||
readonly signals = {
|
readonly signals = {
|
||||||
listAdd: new Signal<WorkspaceLoadEvent>(),
|
listAdd: new Signal<WorkspaceLoadEvent>(),
|
||||||
@@ -62,6 +64,7 @@ export class DataCenter {
|
|||||||
this.signals.listRemove.on(workspace => {
|
this.signals.listRemove.on(workspace => {
|
||||||
this._config.delete(`list:${workspace}`);
|
this._config.delete(`list:${workspace}`);
|
||||||
});
|
});
|
||||||
|
this.workspacesList.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
get apis(): Readonly<Apis> {
|
get apis(): Readonly<Apis> {
|
||||||
@@ -195,10 +198,11 @@ export class DataCenter {
|
|||||||
* data state is also map, the key is the provider id, and the data exists locally when the value is true, otherwise it does not exist
|
* data state is also map, the key is the provider id, and the data exists locally when the value is true, otherwise it does not exist
|
||||||
*/
|
*/
|
||||||
async list(): Promise<Record<string, Record<string, boolean>>> {
|
async list(): Promise<Record<string, Record<string, boolean>>> {
|
||||||
|
const listString = 'list:';
|
||||||
const entries: [string, WorkspaceItem][] = await this._config.entries();
|
const entries: [string, WorkspaceItem][] = await this._config.entries();
|
||||||
return entries.reduce((acc, [k, i]) => {
|
return entries.reduce((acc, [k, i]) => {
|
||||||
if (k.startsWith('list:')) {
|
if (k.startsWith(listString)) {
|
||||||
const key = k.slice(5);
|
const key = k.slice(listString.length);
|
||||||
acc[key] = acc[key] || {};
|
acc[key] = acc[key] || {};
|
||||||
acc[key][i.provider] = i.locally;
|
acc[key][i.provider] = i.locally;
|
||||||
}
|
}
|
||||||
|
|||||||
24
packages/data-center/src/style/index.ts
Normal file
24
packages/data-center/src/style/index.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
export enum WorkspaceType {
|
||||||
|
local = 'local',
|
||||||
|
cloud = 'cloud',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Workspace = {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
isPublish?: boolean;
|
||||||
|
avatar?: string;
|
||||||
|
type: WorkspaceType;
|
||||||
|
owner?: User;
|
||||||
|
isLocal?: boolean;
|
||||||
|
memberCount: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type User = {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
avatar: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WorkspaceMeta = Pick<Workspace, 'name' | 'avatar'>;
|
||||||
1
packages/data-center/src/workspaces/index.ts
Normal file
1
packages/data-center/src/workspaces/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { Workspaces } from './workspaces';
|
||||||
113
packages/data-center/src/workspaces/workspaces.ts
Normal file
113
packages/data-center/src/workspaces/workspaces.ts
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import { Workspace } from '@blocksuite/store';
|
||||||
|
import { Observable } from 'lib0/observable';
|
||||||
|
import { WorkspaceDetail } from 'src/apis/workspace';
|
||||||
|
import { DataCenter } from 'src/datacenter';
|
||||||
|
import { User, Workspace as Wp, WorkspaceType } from 'src/style';
|
||||||
|
|
||||||
|
function getProvider(providerList: Record<string, boolean>) {
|
||||||
|
return Object.keys(providerList)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCloudWorkspace(provider: string) {
|
||||||
|
return provider === 'affine';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Workspaces extends Observable<string> {
|
||||||
|
private _workspaces: Wp[];
|
||||||
|
private readonly workspaceInstances: Map<string, Workspace> = new Map();
|
||||||
|
// cache cloud workspace owner
|
||||||
|
_dc: DataCenter;
|
||||||
|
|
||||||
|
constructor(dataCenter: DataCenter) {
|
||||||
|
super();
|
||||||
|
this._workspaces = [];
|
||||||
|
this.workspaceInstances = new Map();
|
||||||
|
this._dc = dataCenter;
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
// IMP: init local providers
|
||||||
|
this._dc.auth('local');
|
||||||
|
// add listener on list change
|
||||||
|
this._dc.signals.listAdd.on(e => {
|
||||||
|
this.refreshWorkspaceList();
|
||||||
|
});
|
||||||
|
this._dc.signals.listRemove.on(e => {
|
||||||
|
this.refreshWorkspaceList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async refreshWorkspaceList() {
|
||||||
|
const workspaceList = await this._dc.list();
|
||||||
|
|
||||||
|
const workspaceMap = Object.keys(workspaceList).map(([id]) => {
|
||||||
|
return this._dc.load(id).then(w => {
|
||||||
|
return { id, workspace: w, provider: getProvider(workspaceList[id]) };
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const workspaces = (await Promise.all(workspaceMap)).map(w => {
|
||||||
|
const { id, workspace, provider } = w;
|
||||||
|
if (workspace && !this.workspaceInstances.has(id)) {
|
||||||
|
this.workspaceInstances.set(id, workspace);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name: (workspace?.doc?.meta.name as string) || '',
|
||||||
|
avatar: (workspace?.meta.avatar as string) || '',
|
||||||
|
type: isCloudWorkspace(provider)
|
||||||
|
? WorkspaceType.cloud
|
||||||
|
: WorkspaceType.local,
|
||||||
|
isLocal: false,
|
||||||
|
isPublish: false,
|
||||||
|
owner: undefined,
|
||||||
|
memberCount: 1,
|
||||||
|
} as Wp;
|
||||||
|
});
|
||||||
|
const getDetailList = (await Promise.all(workspaceMap)).map(w => {
|
||||||
|
const { id, provider } = w;
|
||||||
|
if (provider === 'workspaces') {
|
||||||
|
return new Promise<{ id: string; detail: WorkspaceDetail | null }>(
|
||||||
|
resolve => {
|
||||||
|
this._dc.apis.getWorkspaceDetail({ id }).then(data => {
|
||||||
|
resolve({ id, detail: data || null });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const ownerList = await Promise.all(getDetailList);
|
||||||
|
(await Promise.all(ownerList)).forEach(detail => {
|
||||||
|
if (detail) {
|
||||||
|
const { id, detail: workspaceDetail } = detail;
|
||||||
|
if (workspaceDetail) {
|
||||||
|
const { owner, member_count } = workspaceDetail;
|
||||||
|
const currentWorkspace = workspaces.find(w => w.id === id);
|
||||||
|
if (currentWorkspace) {
|
||||||
|
currentWorkspace.owner = {
|
||||||
|
id: owner.id,
|
||||||
|
name: owner.name,
|
||||||
|
avatar: owner.avatar_url,
|
||||||
|
email: owner.email,
|
||||||
|
};
|
||||||
|
currentWorkspace.memberCount = member_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._updateWorkspaces(workspaces);
|
||||||
|
}
|
||||||
|
|
||||||
|
getWorkspaces() {
|
||||||
|
return this._workspaces;
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateWorkspaces(workspaces: Wp[]) {
|
||||||
|
this._workspaces = workspaces;
|
||||||
|
this.emit('change', this._workspaces);
|
||||||
|
}
|
||||||
|
|
||||||
|
onWorkspaceChange(cb: (workspace: Wp) => void) {
|
||||||
|
this.on('change', cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user