mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-21 03:56:23 +08:00
Feat/cloud integration (#569)
* Chore/unit test (#538) * chore: add unit test * chore: add github action for unit test * feat: init firebase * chore: add development secrets * fix: rename auth -> data-services * feat: update blocksuite 0.3.0-alpha.4 (#543) * feat: add requests * feat: optimize swr cache * feat: add Authorization * feat: add confirm-invitation page * feat: add account sdk api and proxy * docs: update contributing (#550) * docs: update contributing * Update CONTRIBUTING.md * Update CONTRIBUTING.md Co-authored-by: ShortCipher5 <me@shortcipher.me> * feat: update api * feat: remove babelrc setting * feat: add create workspace ui * feat: choose workspaces * feat: login modal * feat: authorization api * feat: login status * fix: remove unused variables * feat: login button * fix: lint * fix: workspace id * fix: i18n type error Co-authored-by: MingLiang Wang <mingliangwang0o0@gmail.com> Co-authored-by: ShortCipher5 <me@shortcipher.me>
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import useSWR, { SWRConfiguration } from 'swr';
|
||||
import { request, ServiceError } from '../request';
|
||||
import type {
|
||||
RequestAcceptInviting,
|
||||
RequestInviteCollaborator,
|
||||
RequestRemoveCollaborator,
|
||||
ResponseInviteCollaborator,
|
||||
} from './types';
|
||||
|
||||
const COLLABORATOR_INVITE_URL = '/api/account/invite';
|
||||
const REMOVE_COLLABORATOR_URL = '/api/account/remove_collaborator';
|
||||
|
||||
async function doInviteCollaborator(
|
||||
url: string,
|
||||
req: RequestInviteCollaborator
|
||||
) {
|
||||
const { data } = await request.post<ResponseInviteCollaborator>(url, req);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function inviteCollaborator(req: RequestInviteCollaborator) {
|
||||
return await doInviteCollaborator(COLLABORATOR_INVITE_URL, req);
|
||||
}
|
||||
|
||||
async function doRemoveCollaborator(
|
||||
url: string,
|
||||
req: RequestRemoveCollaborator
|
||||
) {
|
||||
return request.post(url, req);
|
||||
}
|
||||
|
||||
export async function removeCollaborator(req: RequestRemoveCollaborator) {
|
||||
return await doRemoveCollaborator(REMOVE_COLLABORATOR_URL, req);
|
||||
}
|
||||
|
||||
function doAcceptInviting(url: string, req: RequestAcceptInviting) {
|
||||
return request.post(url, req);
|
||||
}
|
||||
|
||||
export async function acceptInviting(req: RequestAcceptInviting) {
|
||||
return await doAcceptInviting(COLLABORATOR_INVITE_URL, req);
|
||||
}
|
||||
|
||||
export function useInviteCollaborator(
|
||||
req: RequestInviteCollaborator,
|
||||
config?: SWRConfiguration
|
||||
) {
|
||||
const { data, error } = useSWR<ResponseInviteCollaborator, ServiceError>(
|
||||
[COLLABORATOR_INVITE_URL, req],
|
||||
doInviteCollaborator,
|
||||
config
|
||||
);
|
||||
return {
|
||||
data,
|
||||
isLoading: !error && !data,
|
||||
isError: error,
|
||||
};
|
||||
}
|
||||
|
||||
export function useRemoveCollaborator(
|
||||
req: RequestRemoveCollaborator,
|
||||
config?: SWRConfiguration
|
||||
) {
|
||||
const { data, error } = useSWR<unknown, ServiceError>(
|
||||
[REMOVE_COLLABORATOR_URL, req],
|
||||
doRemoveCollaborator,
|
||||
config
|
||||
);
|
||||
return {
|
||||
data,
|
||||
isLoading: !error && !data,
|
||||
isError: error,
|
||||
};
|
||||
}
|
||||
|
||||
export function useAcceptInviting(
|
||||
req: RequestAcceptInviting,
|
||||
config?: SWRConfiguration
|
||||
) {
|
||||
const { data, error } = useSWR<unknown, ServiceError>(
|
||||
[COLLABORATOR_INVITE_URL, req],
|
||||
doAcceptInviting,
|
||||
config
|
||||
);
|
||||
return {
|
||||
data,
|
||||
isLoading: !error && !data,
|
||||
isError: error,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from './workspace';
|
||||
export * from './workspace.hook';
|
||||
export * from './user';
|
||||
export * from './user.hook';
|
||||
@@ -0,0 +1,33 @@
|
||||
enum WorkspacePermission {
|
||||
read = 'read',
|
||||
write = 'write',
|
||||
}
|
||||
|
||||
export type RequestInviteCollaborator = {
|
||||
email: string;
|
||||
workspace_id: string;
|
||||
};
|
||||
|
||||
export type ResponseInviteCollaborator = {
|
||||
inviting_code: string;
|
||||
};
|
||||
|
||||
export type RequestRemoveCollaborator = {
|
||||
user_id: string;
|
||||
workspace_id: string;
|
||||
};
|
||||
|
||||
export type ResponseRemoveCollaborator = {
|
||||
workspace_id: string;
|
||||
};
|
||||
|
||||
export type RequestAcceptInviting = {
|
||||
inviting_code: string;
|
||||
};
|
||||
|
||||
export type ResponseAcceptInviting = {
|
||||
workspace_id: string;
|
||||
name: string;
|
||||
avatar_url: string;
|
||||
permissions: WorkspacePermission;
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export type CommonError = { error: { code: string; message: string } };
|
||||
export type MayError = Partial<CommonError>;
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './common';
|
||||
export * from './workspace';
|
||||
export * from './account';
|
||||
@@ -0,0 +1,32 @@
|
||||
enum WorkspaceType {
|
||||
'personal' = 'personal',
|
||||
'team' = 'team',
|
||||
}
|
||||
|
||||
export type Workspace = {
|
||||
id: string;
|
||||
name: string;
|
||||
workspace_type: WorkspaceType;
|
||||
avatar_url?: string;
|
||||
public: boolean;
|
||||
};
|
||||
|
||||
export type ResponseGetWorkspaces = {
|
||||
create: Array<Workspace>;
|
||||
read: Array<Workspace>;
|
||||
write: Array<Workspace>;
|
||||
};
|
||||
|
||||
export type RequestCreateWorkspace = {
|
||||
name: string;
|
||||
avatar_url?: string;
|
||||
workspace_type: WorkspaceType;
|
||||
};
|
||||
|
||||
export type RequestUpdateWorkspace = {
|
||||
workspace_type?: WorkspaceType;
|
||||
avatar_url?: string;
|
||||
public?: boolean;
|
||||
};
|
||||
|
||||
export type ResponseCreateWorkspace = any;
|
||||
@@ -0,0 +1,45 @@
|
||||
import useSWR from 'swr';
|
||||
import type { SWRConfiguration } from 'swr';
|
||||
import { login, getUserByEmail } from './user';
|
||||
import type {
|
||||
LoginParams,
|
||||
LoginResponse,
|
||||
GetUserByEmailParams,
|
||||
User,
|
||||
} from './user';
|
||||
|
||||
export const LOGIN_SWR_KEY = 'user.token';
|
||||
export function useLogin(params: LoginParams, config?: SWRConfiguration) {
|
||||
const { data, error, isValidating, isLoading, mutate } =
|
||||
useSWR<LoginResponse>(
|
||||
[LOGIN_SWR_KEY, params],
|
||||
([_, params]) => login(params),
|
||||
config
|
||||
);
|
||||
|
||||
return {
|
||||
loading: isLoading,
|
||||
data,
|
||||
error,
|
||||
mutate,
|
||||
};
|
||||
}
|
||||
|
||||
export const GET_USER_BY_EMAIL_SWR_TOKEN = 'user.getUserByEmail';
|
||||
export function useGetUserByEmail(
|
||||
params: GetUserByEmailParams,
|
||||
config?: SWRConfiguration
|
||||
) {
|
||||
const { data, error, isLoading, mutate } = useSWR<User | null>(
|
||||
[GET_USER_BY_EMAIL_SWR_TOKEN, params],
|
||||
([_, params]) => getUserByEmail(params),
|
||||
config
|
||||
);
|
||||
|
||||
return {
|
||||
loading: isLoading,
|
||||
data,
|
||||
error,
|
||||
mutate,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { request } from '../request';
|
||||
|
||||
export interface ExchangeToken {
|
||||
type: 'Google';
|
||||
/**
|
||||
* Token from firebase.
|
||||
*/
|
||||
token: string;
|
||||
}
|
||||
|
||||
export interface RefreshToken {
|
||||
type: 'Refresh';
|
||||
token: string;
|
||||
}
|
||||
|
||||
export type LoginParams = ExchangeToken | RefreshToken;
|
||||
|
||||
export interface LoginResponse {
|
||||
/**
|
||||
* JWT, expires in a very short time
|
||||
*/
|
||||
token: string;
|
||||
/**
|
||||
* Refresh token
|
||||
*/
|
||||
refresh: string;
|
||||
}
|
||||
|
||||
export async function login(params: LoginParams): Promise<LoginResponse> {
|
||||
const data = await request<LoginResponse>({
|
||||
url: '/api/user/token',
|
||||
method: 'POST',
|
||||
data: params,
|
||||
withAuthorization: false,
|
||||
});
|
||||
return data.data;
|
||||
}
|
||||
|
||||
export interface GetUserByEmailParams {
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: number;
|
||||
name: string;
|
||||
email: string;
|
||||
avatar_url: string;
|
||||
create_at: string;
|
||||
}
|
||||
|
||||
export async function getUserByEmail(
|
||||
params: GetUserByEmailParams
|
||||
): Promise<User | null> {
|
||||
const data = await request<User | null>({
|
||||
url: '/api/user',
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
return data.data;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import useSWR, { SWRConfiguration } from 'swr';
|
||||
import type {
|
||||
MayError,
|
||||
RequestCreateWorkspace,
|
||||
RequestUpdateWorkspace,
|
||||
ResponseCreateWorkspace,
|
||||
ResponseGetWorkspaces,
|
||||
} from './types';
|
||||
import { request, ServiceError } from '../request';
|
||||
|
||||
const WORKSPACE_URL = '/api/workspace';
|
||||
|
||||
async function doGetWorkSpaces(url: string) {
|
||||
const { data } = await request.get<ResponseGetWorkspaces>(url);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function getWorkSpaces() {
|
||||
return await doGetWorkSpaces(WORKSPACE_URL);
|
||||
}
|
||||
|
||||
async function doCreateWorkspace(url: string, req: RequestCreateWorkspace) {
|
||||
const { data } = await request.put<ResponseCreateWorkspace>(url, req);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function createWorkspace(req: RequestCreateWorkspace) {
|
||||
doCreateWorkspace(WORKSPACE_URL, req);
|
||||
}
|
||||
|
||||
async function doUpdateWorkspace(url: string, req: RequestUpdateWorkspace) {
|
||||
const { data } = await request.post<MayError>(url, req);
|
||||
return data;
|
||||
}
|
||||
|
||||
export function updateWorkspace(id: string, req: RequestUpdateWorkspace) {
|
||||
return doUpdateWorkspace(`${WORKSPACE_URL}${id}`, req);
|
||||
}
|
||||
|
||||
async function doDeleteWorkspace(url: string) {
|
||||
const { data } = await request.delete<MayError>(url);
|
||||
return data;
|
||||
}
|
||||
|
||||
export function deleteWorkspace(id: string) {
|
||||
return doDeleteWorkspace(`${WORKSPACE_URL}${id}`);
|
||||
}
|
||||
|
||||
export function useGetWorkspaces(config?: SWRConfiguration) {
|
||||
const { data, error } = useSWR<ResponseGetWorkspaces, ServiceError, string>(
|
||||
WORKSPACE_URL,
|
||||
doGetWorkSpaces,
|
||||
config
|
||||
);
|
||||
return {
|
||||
data,
|
||||
isLoading: !data,
|
||||
isError: data && !error,
|
||||
};
|
||||
}
|
||||
|
||||
export function useCreateWorkspace(
|
||||
req: RequestCreateWorkspace,
|
||||
config?: SWRConfiguration
|
||||
) {
|
||||
const { data, error } = useSWR<ResponseGetWorkspaces, ServiceError>(
|
||||
[WORKSPACE_URL, req],
|
||||
doCreateWorkspace,
|
||||
config
|
||||
);
|
||||
return {
|
||||
data,
|
||||
isLoading: !error && !data,
|
||||
isError: error,
|
||||
};
|
||||
}
|
||||
|
||||
export function useUpdateWorkspace(
|
||||
id: string,
|
||||
req: RequestCreateWorkspace,
|
||||
config?: SWRConfiguration
|
||||
) {
|
||||
const { data, error } = useSWR<MayError, ServiceError>(
|
||||
[`${WORKSPACE_URL}${id}`, req],
|
||||
doUpdateWorkspace,
|
||||
config
|
||||
);
|
||||
return {
|
||||
data,
|
||||
isLoading: !error && !data,
|
||||
isError: error,
|
||||
};
|
||||
}
|
||||
|
||||
export function useDeleteWorkspace(id: string, config?: SWRConfiguration) {
|
||||
const { data, error } = useSWR<MayError, ServiceError>(
|
||||
`${WORKSPACE_URL}${id}`,
|
||||
doDeleteWorkspace,
|
||||
config
|
||||
);
|
||||
return {
|
||||
data,
|
||||
isLoading: !error && !data,
|
||||
isError: error,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import useSWR from 'swr';
|
||||
import type { SWRConfiguration } from 'swr';
|
||||
import {
|
||||
getWorkspaceDetail,
|
||||
updateWorkspace,
|
||||
deleteWorkspace,
|
||||
inviteMember,
|
||||
Workspace,
|
||||
} from './workspace';
|
||||
import {
|
||||
GetWorkspaceDetailParams,
|
||||
WorkspaceDetail,
|
||||
UpdateWorkspaceParams,
|
||||
DeleteWorkspaceParams,
|
||||
InviteMemberParams,
|
||||
getWorkspaces,
|
||||
} from './workspace';
|
||||
|
||||
export const GET_WORKSPACE_DETAIL_SWR_TOKEN = 'workspace.getWorkspaceDetail';
|
||||
export function useGetWorkspaceDetail(
|
||||
params: GetWorkspaceDetailParams,
|
||||
config?: SWRConfiguration
|
||||
) {
|
||||
const { data, error, isLoading, mutate } = useSWR<WorkspaceDetail | null>(
|
||||
[GET_WORKSPACE_DETAIL_SWR_TOKEN, params],
|
||||
([_, params]) => getWorkspaceDetail(params),
|
||||
config
|
||||
);
|
||||
|
||||
return {
|
||||
data,
|
||||
error,
|
||||
loading: isLoading,
|
||||
mutate,
|
||||
};
|
||||
}
|
||||
|
||||
export const GET_WORKSPACES_SWR_TOKEN = 'workspace.getWorkspaces';
|
||||
export function useGetWorkspaces(config?: SWRConfiguration) {
|
||||
const { data, error, isLoading } = useSWR<Workspace[]>(
|
||||
[GET_WORKSPACES_SWR_TOKEN],
|
||||
() => getWorkspaces(),
|
||||
config
|
||||
);
|
||||
|
||||
return {
|
||||
data,
|
||||
error,
|
||||
loading: isLoading,
|
||||
};
|
||||
}
|
||||
|
||||
export const UPDATE_WORKSPACE_SWR_TOKEN = 'workspace.updateWorkspace';
|
||||
/**
|
||||
* I don't think a hook needed for update workspace.
|
||||
* If you figure out the scene, please implement this function.
|
||||
*/
|
||||
export function useUpdateWorkspace() {}
|
||||
|
||||
export const DELETE_WORKSPACE_SWR_TOKEN = 'workspace.deleteWorkspace';
|
||||
/**
|
||||
* I don't think a hook needed for delete workspace.
|
||||
* If you figure out the scene, please implement this function.
|
||||
*/
|
||||
export function useDeleteWorkspace() {}
|
||||
|
||||
export const INVITE_MEMBER_SWR_TOKEN = 'workspace.inviteMember';
|
||||
/**
|
||||
* I don't think a hook needed for invite member.
|
||||
* If you figure out the scene, please implement this function.
|
||||
*/
|
||||
export function useInviteMember() {}
|
||||
@@ -0,0 +1,186 @@
|
||||
import { request } from '../request';
|
||||
import { User } from './user';
|
||||
|
||||
export interface GetWorkspaceDetailParams {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export enum WorkspaceType {
|
||||
Private = 0,
|
||||
Normal = 1,
|
||||
}
|
||||
|
||||
export enum PermissionType {
|
||||
Read = 0,
|
||||
Write = 1,
|
||||
Admin = 2,
|
||||
Owner = 3,
|
||||
}
|
||||
|
||||
export interface Workspace {
|
||||
id: string;
|
||||
type: WorkspaceType;
|
||||
public: boolean;
|
||||
permission_type: PermissionType;
|
||||
create_at: number;
|
||||
}
|
||||
|
||||
export async function getWorkspaces(): Promise<Workspace[]> {
|
||||
const data = await request({
|
||||
url: '/api/workspace',
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
return data.data;
|
||||
}
|
||||
|
||||
export interface WorkspaceDetail extends Workspace {
|
||||
owner: User;
|
||||
member_count: number;
|
||||
}
|
||||
|
||||
export async function getWorkspaceDetail(
|
||||
params: GetWorkspaceDetailParams
|
||||
): Promise<WorkspaceDetail | null> {
|
||||
const data = await request<WorkspaceDetail | null>({
|
||||
url: `/api/workspace/${params.id}`,
|
||||
method: 'PUT',
|
||||
});
|
||||
|
||||
return data.data;
|
||||
}
|
||||
|
||||
export interface Permission {
|
||||
id: number;
|
||||
type: PermissionType;
|
||||
workspace_id: string;
|
||||
accepted: boolean;
|
||||
create_at: number;
|
||||
}
|
||||
|
||||
export interface RegisteredUser extends User {
|
||||
type: 'Registered';
|
||||
}
|
||||
|
||||
export interface UnregisteredUser {
|
||||
type: 'Unregistered';
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface Member extends Permission {
|
||||
user: RegisteredUser | UnregisteredUser;
|
||||
}
|
||||
|
||||
export interface GetWorkspaceMembersParams {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export async function getWorkspaceMembers(
|
||||
params: GetWorkspaceDetailParams
|
||||
): Promise<Member[]> {
|
||||
const data = await request<Member[]>({
|
||||
url: `/api/workspace/${params.id}/permission`,
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
return data.data;
|
||||
}
|
||||
|
||||
export interface CreateWorkspaceParams {
|
||||
name: string;
|
||||
avatar: string;
|
||||
}
|
||||
|
||||
export async function createWorkspace(
|
||||
params: CreateWorkspaceParams
|
||||
): Promise<void> {
|
||||
const data = await request({
|
||||
url: '/api/workspace',
|
||||
method: 'POST',
|
||||
data: params,
|
||||
});
|
||||
|
||||
return data.data;
|
||||
}
|
||||
|
||||
export interface UpdateWorkspaceParams {
|
||||
id: string;
|
||||
public: boolean;
|
||||
}
|
||||
|
||||
export async function updateWorkspace(
|
||||
params: UpdateWorkspaceParams
|
||||
): Promise<void> {
|
||||
const data = await request({
|
||||
url: `/api/workspace/${params.id}`,
|
||||
method: 'POST',
|
||||
data: {
|
||||
public: params.public,
|
||||
},
|
||||
});
|
||||
|
||||
return data.data;
|
||||
}
|
||||
|
||||
export interface DeleteWorkspaceParams {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export async function deleteWorkspace(
|
||||
params: DeleteWorkspaceParams
|
||||
): Promise<void> {
|
||||
const data = await request({
|
||||
url: `/api/workspace/${params.id}`,
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
return data.data;
|
||||
}
|
||||
|
||||
export interface InviteMemberParams {
|
||||
id: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notice: Only support normal(contrast to private) workspace.
|
||||
*/
|
||||
export async function inviteMember(params: InviteMemberParams): Promise<void> {
|
||||
const data = await request({
|
||||
url: `/api/workspace/${params.id}/permission`,
|
||||
method: 'POST',
|
||||
data: {
|
||||
email: params.email,
|
||||
},
|
||||
});
|
||||
|
||||
return data.data;
|
||||
}
|
||||
|
||||
export interface RemoveMemberParams {
|
||||
permissionId: number;
|
||||
}
|
||||
|
||||
export async function removeMember(params: RemoveMemberParams): Promise<void> {
|
||||
const data = await request({
|
||||
url: `/api/permission/${params.permissionId}`,
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
return data.data;
|
||||
}
|
||||
|
||||
export interface AcceptInvitingParams {
|
||||
invitingCode: string;
|
||||
}
|
||||
|
||||
export async function acceptInviting(
|
||||
params: AcceptInvitingParams
|
||||
): Promise<void> {
|
||||
const data = await request({
|
||||
url: `/api/invite/${params.invitingCode}`,
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
return data.data;
|
||||
}
|
||||
Reference in New Issue
Block a user