mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-16 17:46:18 +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,41 @@
|
||||
import { initializeApp } from 'firebase/app';
|
||||
import {
|
||||
getAuth,
|
||||
createUserWithEmailAndPassword,
|
||||
signInWithEmailAndPassword,
|
||||
GoogleAuthProvider,
|
||||
signInWithPopup,
|
||||
} from 'firebase/auth';
|
||||
import type { User } from 'firebase/auth';
|
||||
|
||||
/**
|
||||
* firebaseConfig reference: https://firebase.google.com/docs/web/setup#add_firebase_to_your_app
|
||||
*/
|
||||
const app = initializeApp({
|
||||
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
|
||||
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
|
||||
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
|
||||
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
|
||||
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
|
||||
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
|
||||
measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
|
||||
});
|
||||
|
||||
export const firebaseAuth = getAuth(app);
|
||||
|
||||
const signUp = (email: string, password: string) => {
|
||||
return createUserWithEmailAndPassword(firebaseAuth, email, password);
|
||||
};
|
||||
|
||||
const signIn = (email: string, password: string) => {
|
||||
return signInWithEmailAndPassword(firebaseAuth, email, password);
|
||||
};
|
||||
|
||||
const googleAuthProvider = new GoogleAuthProvider();
|
||||
export const signInWithGoogle = () => {
|
||||
return signInWithPopup(firebaseAuth, googleAuthProvider);
|
||||
};
|
||||
|
||||
export const onAuthStateChanged = (callback: (user: User | null) => void) => {
|
||||
firebaseAuth.onAuthStateChanged(callback);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export { signInWithGoogle, onAuthStateChanged } from './auth';
|
||||
export * from './request';
|
||||
export * from './sdks';
|
||||
@@ -0,0 +1,9 @@
|
||||
export class ServiceError extends Error {
|
||||
public message: string;
|
||||
public code: string;
|
||||
constructor(code: string, message: string) {
|
||||
super(message);
|
||||
this.message = message;
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import axios from 'axios';
|
||||
import { setAuthorization } from './request';
|
||||
import { handleResponseError } from './response';
|
||||
|
||||
declare module 'axios' {
|
||||
interface AxiosRequestConfig {
|
||||
/**
|
||||
* If true, request will send with Authorization header.
|
||||
*/
|
||||
withAuthorization?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
export const request = axios.create({
|
||||
withAuthorization: true,
|
||||
});
|
||||
|
||||
request.interceptors.request.use(setAuthorization);
|
||||
request.interceptors.response.use(handleResponseError);
|
||||
@@ -0,0 +1,4 @@
|
||||
export { request } from './axios';
|
||||
export { ServiceError } from './ServiceError';
|
||||
export { setToken, authorizationEvent } from './request';
|
||||
export type { AccessTokenMessage } from './request';
|
||||
@@ -0,0 +1,99 @@
|
||||
import type { AxiosRequestConfig } from 'axios';
|
||||
import { login } from '../../../sdks';
|
||||
import { AuthorizationEvent } from './events';
|
||||
import type { AccessTokenMessage } from './types';
|
||||
|
||||
const TOKEN_KEY = 'affine_token';
|
||||
|
||||
export const authorizationEvent = new AuthorizationEvent();
|
||||
authorizationEvent.triggerChange(
|
||||
parseAccessToken(getToken()?.accessToken || '')
|
||||
);
|
||||
|
||||
interface Token {
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* set null to clear token
|
||||
*/
|
||||
export function setToken(token: Token | null): void {
|
||||
if (token === null) {
|
||||
window.localStorage.removeItem(TOKEN_KEY);
|
||||
authorizationEvent.triggerChange(null);
|
||||
return;
|
||||
}
|
||||
window.localStorage.setItem(TOKEN_KEY, JSON.stringify(token));
|
||||
authorizationEvent.triggerChange(parseAccessToken(token.accessToken));
|
||||
}
|
||||
|
||||
function getToken(): { accessToken: string; refreshToken: string } | null {
|
||||
try {
|
||||
return JSON.parse(window.localStorage.getItem(TOKEN_KEY) || '');
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function parseAccessToken(token: string): AccessTokenMessage | null {
|
||||
try {
|
||||
const message: AccessTokenMessage = JSON.parse(
|
||||
window.atob(token.split('.')[1])
|
||||
);
|
||||
return message;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function isAccessTokenExpired(token: string) {
|
||||
const message = parseAccessToken(token);
|
||||
if (!message) {
|
||||
return true;
|
||||
}
|
||||
return Date.now() - message.create_at > message.exp;
|
||||
}
|
||||
|
||||
async function isLoggedIn(): Promise<boolean> {
|
||||
const token = getToken();
|
||||
if (!token) {
|
||||
return false;
|
||||
}
|
||||
return isAccessTokenExpired(token.accessToken);
|
||||
}
|
||||
|
||||
let refreshingToken: ReturnType<typeof login> | undefined;
|
||||
|
||||
export async function setAuthorization(config: AxiosRequestConfig<unknown>) {
|
||||
if (!config.headers) {
|
||||
config.headers = {};
|
||||
}
|
||||
|
||||
if (config.withAuthorization) {
|
||||
let token = getToken();
|
||||
if (!token) {
|
||||
throw new Error('No authorization token.');
|
||||
}
|
||||
if (isAccessTokenExpired(token.accessToken)) {
|
||||
if (!refreshingToken) {
|
||||
refreshingToken = login({
|
||||
type: 'Refresh',
|
||||
token: token.refreshToken,
|
||||
});
|
||||
}
|
||||
const newToken = await refreshingToken;
|
||||
token = {
|
||||
accessToken: newToken.token,
|
||||
refreshToken: newToken.refresh,
|
||||
};
|
||||
setToken(token);
|
||||
refreshingToken = undefined;
|
||||
}
|
||||
if (!config.headers) {
|
||||
config.headers = {};
|
||||
}
|
||||
config.headers['Authorization'] = token.accessToken;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { AccessTokenMessage } from './types';
|
||||
|
||||
type Callback = (user: AccessTokenMessage | null) => void;
|
||||
|
||||
export class AuthorizationEvent {
|
||||
private callbacks: Callback[] = [];
|
||||
private lastState: AccessTokenMessage | null = null;
|
||||
|
||||
/**
|
||||
* Callback will execute when call this function.
|
||||
*/
|
||||
onChange(callback: Callback) {
|
||||
this.callbacks.push(callback);
|
||||
callback(this.lastState);
|
||||
}
|
||||
|
||||
triggerChange(user: AccessTokenMessage | null) {
|
||||
this.lastState = user;
|
||||
this.callbacks.forEach(callback => callback(user));
|
||||
}
|
||||
|
||||
removeCallback(callback: Callback) {
|
||||
const index = this.callbacks.indexOf(callback);
|
||||
if (index > -1) {
|
||||
this.callbacks.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './authorization';
|
||||
export type { AccessTokenMessage } from './types';
|
||||
@@ -0,0 +1,8 @@
|
||||
export interface AccessTokenMessage {
|
||||
create_at: number;
|
||||
exp: number;
|
||||
email: string;
|
||||
id: number;
|
||||
name: string;
|
||||
avatar_url: string;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export {
|
||||
setAuthorization,
|
||||
setToken,
|
||||
authorizationEvent,
|
||||
} from './authorization';
|
||||
export type { AccessTokenMessage } from './authorization';
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { AxiosResponse } from 'axios';
|
||||
import { ServiceError } from '../ServiceError';
|
||||
|
||||
export function handleResponseError(response: AxiosResponse<any, any>) {
|
||||
const { data, status } = response;
|
||||
if (status === 200) {
|
||||
if (data.error) {
|
||||
// TODO - common error handling
|
||||
const error = new ServiceError(data.error.message, data.error.code);
|
||||
throw error;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { handleResponseError } from './handle-error';
|
||||
@@ -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