mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-11-27 17:32:37 +03:00
feat(core): make permission and invoice offline available (#8123)
This commit is contained in:
parent
8be67dce82
commit
f4db4058f8
@ -75,6 +75,14 @@ export class PermissionService {
|
||||
return owner.user;
|
||||
}
|
||||
|
||||
async getWorkspaceMemberCount(workspaceId: string) {
|
||||
return this.prisma.workspaceUserPermission.count({
|
||||
where: {
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async tryGetWorkspaceOwner(workspaceId: string) {
|
||||
return this.prisma.workspaceUserPermission.findFirst({
|
||||
where: {
|
||||
|
@ -113,6 +113,8 @@ export class QuotaManagementService {
|
||||
// quota was apply to owner's account
|
||||
async getWorkspaceUsage(workspaceId: string): Promise<QuotaBusinessType> {
|
||||
const owner = await this.permissions.getWorkspaceOwner(workspaceId);
|
||||
const memberCount =
|
||||
await this.permissions.getWorkspaceMemberCount(workspaceId);
|
||||
const {
|
||||
feature: {
|
||||
name,
|
||||
@ -145,6 +147,7 @@ export class QuotaManagementService {
|
||||
humanReadable,
|
||||
usedSize,
|
||||
unlimited,
|
||||
memberCount,
|
||||
};
|
||||
|
||||
if (quota.unlimited) {
|
||||
|
@ -87,6 +87,9 @@ export class QuotaQueryType {
|
||||
@Field(() => SafeIntResolver)
|
||||
memberLimit!: number;
|
||||
|
||||
@Field(() => SafeIntResolver)
|
||||
memberCount!: number;
|
||||
|
||||
@Field(() => SafeIntResolver)
|
||||
storageQuota!: number;
|
||||
|
||||
|
@ -115,11 +115,7 @@ export class WorkspaceResolver {
|
||||
complexity: 2,
|
||||
})
|
||||
memberCount(@Parent() workspace: WorkspaceType) {
|
||||
return this.prisma.workspaceUserPermission.count({
|
||||
where: {
|
||||
workspaceId: workspace.id,
|
||||
},
|
||||
});
|
||||
return this.permissions.getWorkspaceMemberCount(workspace.id);
|
||||
}
|
||||
|
||||
@ResolveField(() => Boolean, {
|
||||
@ -388,13 +384,8 @@ export class WorkspaceResolver {
|
||||
}
|
||||
|
||||
// member limit check
|
||||
const [memberCount, quota] = await Promise.all([
|
||||
this.prisma.workspaceUserPermission.count({
|
||||
where: { workspaceId },
|
||||
}),
|
||||
this.quota.getWorkspaceUsage(workspaceId),
|
||||
]);
|
||||
if (memberCount >= quota.memberLimit) {
|
||||
const quota = await this.quota.getWorkspaceUsage(workspaceId);
|
||||
if (quota.memberCount >= quota.memberLimit) {
|
||||
return new MemberQuotaExceeded();
|
||||
}
|
||||
|
||||
|
@ -603,6 +603,7 @@ type QuotaQueryType {
|
||||
copilotActionLimit: SafeInt
|
||||
historyPeriod: SafeInt!
|
||||
humanReadable: HumanReadableQuotaType!
|
||||
memberCount: SafeInt!
|
||||
memberLimit: SafeInt!
|
||||
name: String!
|
||||
storageQuota: SafeInt!
|
||||
|
@ -6,19 +6,21 @@ import ReactPaginate from 'react-paginate';
|
||||
import * as styles from './styles.css';
|
||||
export interface PaginationProps {
|
||||
totalCount: number;
|
||||
pageNum?: number;
|
||||
countPerPage: number;
|
||||
onPageChange: (skip: number) => void;
|
||||
onPageChange: (skip: number, pageNum: number) => void;
|
||||
}
|
||||
|
||||
export const Pagination = ({
|
||||
totalCount,
|
||||
countPerPage,
|
||||
pageNum,
|
||||
onPageChange,
|
||||
}: PaginationProps) => {
|
||||
const handlePageClick = useCallback(
|
||||
(e: { selected: number }) => {
|
||||
const newOffset = (e.selected * countPerPage) % totalCount;
|
||||
onPageChange(newOffset);
|
||||
onPageChange(newOffset, e.selected);
|
||||
},
|
||||
[countPerPage, onPageChange, totalCount]
|
||||
);
|
||||
@ -34,6 +36,7 @@ export const Pagination = ({
|
||||
pageRangeDisplayed={3}
|
||||
marginPagesDisplayed={2}
|
||||
pageCount={pageCount}
|
||||
forcePage={pageNum}
|
||||
previousLabel={<ArrowLeftSmallIcon />}
|
||||
nextLabel={<ArrowRightSmallIcon />}
|
||||
pageClassName={styles.pageItem}
|
||||
|
@ -1,8 +1,5 @@
|
||||
import { notify } from '@affine/component';
|
||||
import type {
|
||||
InviteModalProps,
|
||||
PaginationProps,
|
||||
} from '@affine/component/member-components';
|
||||
import type { InviteModalProps } from '@affine/component/member-components';
|
||||
import {
|
||||
InviteModal,
|
||||
MemberLimitModal,
|
||||
@ -17,15 +14,16 @@ import { Tooltip } from '@affine/component/ui/tooltip';
|
||||
import { openSettingModalAtom } from '@affine/core/atoms';
|
||||
import { AffineErrorBoundary } from '@affine/core/components/affine/affine-error-boundary';
|
||||
import { useInviteMember } from '@affine/core/hooks/affine/use-invite-member';
|
||||
import { useMemberCount } from '@affine/core/hooks/affine/use-member-count';
|
||||
import type { Member } from '@affine/core/hooks/affine/use-members';
|
||||
import { useMembers } from '@affine/core/hooks/affine/use-members';
|
||||
import { useRevokeMemberPermission } from '@affine/core/hooks/affine/use-revoke-member-permission';
|
||||
import { track } from '@affine/core/mixpanel';
|
||||
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
|
||||
import {
|
||||
type Member,
|
||||
WorkspaceMembersService,
|
||||
WorkspacePermissionService,
|
||||
} from '@affine/core/modules/permissions';
|
||||
import { WorkspaceQuotaService } from '@affine/core/modules/quota';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { Permission } from '@affine/graphql';
|
||||
import { Permission, UserFriendlyError } from '@affine/graphql';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { MoreVerticalIcon } from '@blocksuite/icons/rc';
|
||||
import {
|
||||
@ -34,18 +32,11 @@ import {
|
||||
useService,
|
||||
WorkspaceService,
|
||||
} from '@toeverything/infra';
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import clsx from 'clsx';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import type { ReactElement } from 'react';
|
||||
import {
|
||||
Suspense,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import {
|
||||
type AuthAccountInfo,
|
||||
@ -55,7 +46,6 @@ import {
|
||||
} from '../../../../../modules/cloud';
|
||||
import * as style from './style.css';
|
||||
|
||||
const COUNT_PER_PAGE = 8;
|
||||
type OnRevoke = (memberId: string) => void;
|
||||
const MembersPanelLocal = () => {
|
||||
const t = useI18n();
|
||||
@ -76,7 +66,6 @@ export const CloudWorkspaceMembersPanel = () => {
|
||||
serverConfig.features$.map(f => f?.payment)
|
||||
);
|
||||
const workspace = useService(WorkspaceService).workspace;
|
||||
const memberCount = useMemberCount(workspace.id);
|
||||
|
||||
const permissionService = useService(WorkspacePermissionService);
|
||||
const isOwner = useLiveData(permissionService.permission.isOwner$);
|
||||
@ -84,42 +73,32 @@ export const CloudWorkspaceMembersPanel = () => {
|
||||
permissionService.permission.revalidate();
|
||||
}, [permissionService]);
|
||||
|
||||
const checkMemberCountLimit = useCallback(
|
||||
(memberCount: number, memberLimit?: number) => {
|
||||
if (memberLimit === undefined) return false;
|
||||
return memberCount >= memberLimit;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const workspaceQuotaService = useService(WorkspaceQuotaService);
|
||||
useEffect(() => {
|
||||
workspaceQuotaService.quota.revalidate();
|
||||
}, [workspaceQuotaService]);
|
||||
const isLoading = useLiveData(workspaceQuotaService.quota.isLoading$);
|
||||
const error = useLiveData(workspaceQuotaService.quota.error$);
|
||||
const workspaceQuota = useLiveData(workspaceQuotaService.quota.quota$);
|
||||
const subscriptionService = useService(SubscriptionService);
|
||||
const plan = useLiveData(
|
||||
subscriptionService.subscription.pro$.map(s => s?.plan)
|
||||
);
|
||||
const isLimited = workspaceQuota
|
||||
? checkMemberCountLimit(memberCount, workspaceQuota.memberLimit)
|
||||
: null;
|
||||
const isLimited =
|
||||
workspaceQuota && workspaceQuota.memberLimit
|
||||
? workspaceQuota.memberCount >= workspaceQuota.memberLimit
|
||||
: null;
|
||||
|
||||
const t = useI18n();
|
||||
const { invite, isMutating } = useInviteMember(workspace.id);
|
||||
const revokeMemberPermission = useRevokeMemberPermission(workspace.id);
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [memberSkip, setMemberSkip] = useState(0);
|
||||
|
||||
const openModal = useCallback(() => {
|
||||
setOpen(true);
|
||||
}, []);
|
||||
|
||||
const onPageChange = useCallback<PaginationProps['onPageChange']>(offset => {
|
||||
setMemberSkip(offset);
|
||||
}, []);
|
||||
|
||||
const onInviteConfirm = useCallback<InviteModalProps['onConfirm']>(
|
||||
async ({ email, permission }) => {
|
||||
const success = await invite(
|
||||
@ -151,20 +130,6 @@ export const CloudWorkspaceMembersPanel = () => {
|
||||
});
|
||||
}, [setSettingModalAtom]);
|
||||
|
||||
const listContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
const [memberListHeight, setMemberListHeight] = useState<number | null>(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (
|
||||
memberCount > COUNT_PER_PAGE &&
|
||||
listContainerRef.current &&
|
||||
memberListHeight === null
|
||||
) {
|
||||
const rect = listContainerRef.current.getBoundingClientRect();
|
||||
setMemberListHeight(rect.height);
|
||||
}
|
||||
}, [listContainerRef, memberCount, memberListHeight]);
|
||||
|
||||
const onRevoke = useCallback<OnRevoke>(
|
||||
async memberId => {
|
||||
const res = await revokeMemberPermission(memberId);
|
||||
@ -195,14 +160,23 @@ export const CloudWorkspaceMembersPanel = () => {
|
||||
}, [handleUpgradeConfirm, hasPaymentFeature, t, workspaceQuota]);
|
||||
|
||||
if (workspaceQuota === null) {
|
||||
// TODO(@eyhn): loading ui
|
||||
return null;
|
||||
if (isLoading) {
|
||||
return <MembersPanelFallback />;
|
||||
}
|
||||
if (error) {
|
||||
return (
|
||||
<span style={{ color: cssVar('errorColor') }}>
|
||||
{UserFriendlyError.fromAnyError(error).message}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return; // never reach here
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingRow
|
||||
name={`${t['Members']()} (${memberCount}/${workspaceQuota.humanReadable.memberLimit})`}
|
||||
name={`${t['Members']()} (${workspaceQuota.memberCount}/${workspaceQuota.humanReadable.memberLimit})`}
|
||||
desc={desc}
|
||||
spreadCol={!!isOwner}
|
||||
>
|
||||
@ -230,27 +204,8 @@ export const CloudWorkspaceMembersPanel = () => {
|
||||
) : null}
|
||||
</SettingRow>
|
||||
|
||||
<div
|
||||
className={style.membersPanel}
|
||||
ref={listContainerRef}
|
||||
style={memberListHeight ? { height: memberListHeight } : {}}
|
||||
>
|
||||
<Suspense fallback={<MemberListFallback memberCount={memberCount} />}>
|
||||
<MemberList
|
||||
workspaceId={workspace.id}
|
||||
isOwner={!!isOwner}
|
||||
skip={memberSkip}
|
||||
onRevoke={onRevoke}
|
||||
/>
|
||||
</Suspense>
|
||||
|
||||
{memberCount > COUNT_PER_PAGE && (
|
||||
<Pagination
|
||||
totalCount={memberCount}
|
||||
countPerPage={COUNT_PER_PAGE}
|
||||
onPageChange={onPageChange}
|
||||
/>
|
||||
)}
|
||||
<div className={style.membersPanel}>
|
||||
<MemberList isOwner={!!isOwner} onRevoke={onRevoke} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@ -271,12 +226,12 @@ export const MembersPanelFallback = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const MemberListFallback = ({ memberCount }: { memberCount: number }) => {
|
||||
const MemberListFallback = ({ memberCount }: { memberCount?: number }) => {
|
||||
// prevent page jitter
|
||||
const height = useMemo(() => {
|
||||
if (memberCount > COUNT_PER_PAGE) {
|
||||
if (memberCount) {
|
||||
// height and margin-bottom
|
||||
return COUNT_PER_PAGE * 58 + (COUNT_PER_PAGE - 1) * 6;
|
||||
return memberCount * 58 + (memberCount - 1) * 6;
|
||||
}
|
||||
return 'auto';
|
||||
}, [memberCount]);
|
||||
@ -296,31 +251,66 @@ const MemberListFallback = ({ memberCount }: { memberCount: number }) => {
|
||||
};
|
||||
|
||||
const MemberList = ({
|
||||
workspaceId,
|
||||
isOwner,
|
||||
skip,
|
||||
onRevoke,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
isOwner: boolean;
|
||||
skip: number;
|
||||
onRevoke: OnRevoke;
|
||||
}) => {
|
||||
const members = useMembers(workspaceId, skip, COUNT_PER_PAGE);
|
||||
const membersService = useService(WorkspaceMembersService);
|
||||
const memberCount = useLiveData(membersService.members.memberCount$);
|
||||
const pageNum = useLiveData(membersService.members.pageNum$);
|
||||
const isLoading = useLiveData(membersService.members.isLoading$);
|
||||
const pageMembers = useLiveData(membersService.members.pageMembers$);
|
||||
|
||||
useEffect(() => {
|
||||
membersService.members.revalidate();
|
||||
}, [membersService]);
|
||||
|
||||
const session = useService(AuthService).session;
|
||||
const account = useEnsureLiveData(session.account$);
|
||||
|
||||
const handlePageChange = useCallback(
|
||||
(_: number, pageNum: number) => {
|
||||
membersService.members.setPageNum(pageNum);
|
||||
membersService.members.revalidate();
|
||||
},
|
||||
[membersService]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={style.memberList}>
|
||||
{members.map(member => (
|
||||
<MemberItem
|
||||
currentAccount={account}
|
||||
key={member.id}
|
||||
member={member}
|
||||
isOwner={isOwner}
|
||||
onRevoke={onRevoke}
|
||||
{isLoading && pageMembers === undefined ? (
|
||||
<MemberListFallback
|
||||
memberCount={
|
||||
memberCount
|
||||
? Math.max(
|
||||
memberCount - pageNum * membersService.members.PAGE_SIZE,
|
||||
1
|
||||
)
|
||||
: 1
|
||||
}
|
||||
/>
|
||||
))}
|
||||
) : (
|
||||
pageMembers?.map(member => (
|
||||
<MemberItem
|
||||
currentAccount={account}
|
||||
key={member.id}
|
||||
member={member}
|
||||
isOwner={isOwner}
|
||||
onRevoke={onRevoke}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
{memberCount !== undefined &&
|
||||
memberCount > membersService.members.PAGE_SIZE && (
|
||||
<Pagination
|
||||
totalCount={memberCount}
|
||||
countPerPage={membersService.members.PAGE_SIZE}
|
||||
pageNum={pageNum}
|
||||
onPageChange={handlePageChange}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -409,9 +399,7 @@ export const MembersPanel = (): ReactElement | null => {
|
||||
}
|
||||
return (
|
||||
<AffineErrorBoundary>
|
||||
<Suspense fallback={<MembersPanelFallback />}>
|
||||
<CloudWorkspaceMembersPanel />
|
||||
</Suspense>
|
||||
<CloudWorkspaceMembersPanel />
|
||||
</AffineErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
@ -1,14 +0,0 @@
|
||||
import { getMemberCountByWorkspaceIdQuery } from '@affine/graphql';
|
||||
|
||||
import { useQuery } from '../use-query';
|
||||
|
||||
export function useMemberCount(workspaceId: string) {
|
||||
const { data } = useQuery({
|
||||
query: getMemberCountByWorkspaceIdQuery,
|
||||
variables: {
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
return data.workspace.memberCount;
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
import type { GetMembersByWorkspaceIdQuery } from '@affine/graphql';
|
||||
import { getMembersByWorkspaceIdQuery, Permission } from '@affine/graphql';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { useQuery } from '../use-query';
|
||||
|
||||
export function calculateWeight(member: Member) {
|
||||
const permissionWeight = {
|
||||
[Permission.Owner]: 4,
|
||||
[Permission.Admin]: 3,
|
||||
[Permission.Write]: 2,
|
||||
[Permission.Read]: 1,
|
||||
};
|
||||
|
||||
const factors = [
|
||||
Number(member.permission === Permission.Owner), // Owner weight is the highest
|
||||
Number(!member.accepted), // Unaccepted members are before accepted members
|
||||
permissionWeight[member.permission] || 0,
|
||||
];
|
||||
|
||||
return factors.reduce((ret, factor, index, arr) => {
|
||||
return ret + factor * Math.pow(10, arr.length - 1 - index);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
export type Member = Omit<
|
||||
GetMembersByWorkspaceIdQuery['workspace']['members'][number],
|
||||
'__typename'
|
||||
>;
|
||||
export function useMembers(
|
||||
workspaceId: string,
|
||||
skip: number,
|
||||
take: number = 8
|
||||
) {
|
||||
const { data } = useQuery({
|
||||
query: getMembersByWorkspaceIdQuery,
|
||||
variables: {
|
||||
workspaceId,
|
||||
skip,
|
||||
take,
|
||||
},
|
||||
});
|
||||
|
||||
const members = data.workspace.members;
|
||||
|
||||
return useMemo(() => {
|
||||
// sort members by weight
|
||||
return members.sort((a, b) => {
|
||||
const weightDifference = calculateWeight(b) - calculateWeight(a);
|
||||
if (weightDifference !== 0) {
|
||||
return weightDifference;
|
||||
}
|
||||
// if weight is the same, sort by name
|
||||
if (a.name === null) return 1;
|
||||
if (b.name === null) return -1;
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
}, [members]);
|
||||
}
|
@ -3,7 +3,6 @@ import {
|
||||
type GraphQLQuery,
|
||||
type QueryOptions,
|
||||
type QueryResponse,
|
||||
UserFriendlyError,
|
||||
} from '@affine/graphql';
|
||||
import { fromPromise, Service } from '@toeverything/infra';
|
||||
import type { Observable } from 'rxjs';
|
||||
@ -39,13 +38,11 @@ export class GraphQLService extends Service {
|
||||
try {
|
||||
return await this.rawGql(options);
|
||||
} catch (err) {
|
||||
const standardError = UserFriendlyError.fromAnyError(err);
|
||||
|
||||
if (standardError.status === 403) {
|
||||
if (err instanceof BackendError && err.status === 403) {
|
||||
this.framework.get(AuthService).session.revalidate();
|
||||
}
|
||||
|
||||
throw new BackendError(standardError);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -0,0 +1,79 @@
|
||||
import type { GetMembersByWorkspaceIdQuery } from '@affine/graphql';
|
||||
import type { WorkspaceService } from '@toeverything/infra';
|
||||
import {
|
||||
backoffRetry,
|
||||
catchErrorInto,
|
||||
effect,
|
||||
Entity,
|
||||
fromPromise,
|
||||
LiveData,
|
||||
onComplete,
|
||||
onStart,
|
||||
} from '@toeverything/infra';
|
||||
import { distinctUntilChanged, EMPTY, map, mergeMap, switchMap } from 'rxjs';
|
||||
|
||||
import { isBackendError, isNetworkError } from '../../cloud';
|
||||
import type { WorkspaceMembersStore } from '../stores/members';
|
||||
|
||||
export type Member =
|
||||
GetMembersByWorkspaceIdQuery['workspace']['members'][number];
|
||||
|
||||
export class WorkspaceMembers extends Entity {
|
||||
constructor(
|
||||
private readonly store: WorkspaceMembersStore,
|
||||
private readonly workspaceService: WorkspaceService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
pageNum$ = new LiveData(0);
|
||||
memberCount$ = new LiveData<number | undefined>(undefined);
|
||||
pageMembers$ = new LiveData<Member[] | undefined>(undefined);
|
||||
|
||||
isLoading$ = new LiveData(false);
|
||||
error$ = new LiveData<any>(null);
|
||||
|
||||
readonly PAGE_SIZE = 8;
|
||||
|
||||
readonly revalidate = effect(
|
||||
map(() => this.pageNum$.value),
|
||||
distinctUntilChanged<number>(),
|
||||
switchMap(pageNum => {
|
||||
return fromPromise(async signal => {
|
||||
return this.store.fetchMembers(
|
||||
this.workspaceService.workspace.id,
|
||||
pageNum * this.PAGE_SIZE,
|
||||
this.PAGE_SIZE,
|
||||
signal
|
||||
);
|
||||
}).pipe(
|
||||
mergeMap(data => {
|
||||
this.memberCount$.setValue(data.memberCount);
|
||||
this.pageMembers$.setValue(data.members);
|
||||
return EMPTY;
|
||||
}),
|
||||
backoffRetry({
|
||||
when: isNetworkError,
|
||||
count: Infinity,
|
||||
}),
|
||||
backoffRetry({
|
||||
when: isBackendError,
|
||||
}),
|
||||
catchErrorInto(this.error$),
|
||||
onStart(() => {
|
||||
this.pageMembers$.setValue(undefined);
|
||||
this.isLoading$.setValue(true);
|
||||
}),
|
||||
onComplete(() => this.isLoading$.setValue(false))
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
setPageNum(pageNum: number) {
|
||||
this.pageNum$.setValue(pageNum);
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
this.revalidate.unsubscribe();
|
||||
}
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
export type { Member } from './entities/members';
|
||||
export { WorkspaceMembersService } from './services/members';
|
||||
export { WorkspacePermissionService } from './services/permission';
|
||||
|
||||
import { GraphQLService } from '@affine/core/modules/cloud';
|
||||
@ -8,8 +10,11 @@ import {
|
||||
WorkspacesService,
|
||||
} from '@toeverything/infra';
|
||||
|
||||
import { WorkspaceMembers } from './entities/members';
|
||||
import { WorkspacePermission } from './entities/permission';
|
||||
import { WorkspaceMembersService } from './services/members';
|
||||
import { WorkspacePermissionService } from './services/permission';
|
||||
import { WorkspaceMembersStore } from './stores/members';
|
||||
import { WorkspacePermissionStore } from './stores/permission';
|
||||
|
||||
export function configurePermissionsModule(framework: Framework) {
|
||||
@ -21,5 +26,8 @@ export function configurePermissionsModule(framework: Framework) {
|
||||
WorkspacePermissionStore,
|
||||
])
|
||||
.store(WorkspacePermissionStore, [GraphQLService])
|
||||
.entity(WorkspacePermission, [WorkspaceService, WorkspacePermissionStore]);
|
||||
.entity(WorkspacePermission, [WorkspaceService, WorkspacePermissionStore])
|
||||
.service(WorkspaceMembersService)
|
||||
.store(WorkspaceMembersStore, [GraphQLService])
|
||||
.entity(WorkspaceMembers, [WorkspaceMembersStore, WorkspaceService]);
|
||||
}
|
||||
|
@ -0,0 +1,7 @@
|
||||
import { Service } from '@toeverything/infra';
|
||||
|
||||
import { WorkspaceMembers } from '../entities/members';
|
||||
|
||||
export class WorkspaceMembersService extends Service {
|
||||
members = this.framework.createEntity(WorkspaceMembers);
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
import { getMembersByWorkspaceIdQuery } from '@affine/graphql';
|
||||
import { Store } from '@toeverything/infra';
|
||||
|
||||
import type { GraphQLService } from '../../cloud';
|
||||
|
||||
export class WorkspaceMembersStore extends Store {
|
||||
constructor(private readonly graphqlService: GraphQLService) {
|
||||
super();
|
||||
}
|
||||
|
||||
async fetchMembers(
|
||||
workspaceId: string,
|
||||
skip: number,
|
||||
take: number,
|
||||
signal?: AbortSignal
|
||||
) {
|
||||
const data = await this.graphqlService.gql({
|
||||
query: getMembersByWorkspaceIdQuery,
|
||||
variables: {
|
||||
workspaceId,
|
||||
skip,
|
||||
take,
|
||||
},
|
||||
context: {
|
||||
signal,
|
||||
},
|
||||
});
|
||||
|
||||
return data.workspace;
|
||||
}
|
||||
}
|
@ -1297,6 +1297,7 @@ query workspaceQuota($id: String!) {
|
||||
storageQuota
|
||||
historyPeriod
|
||||
memberLimit
|
||||
memberCount
|
||||
humanReadable {
|
||||
name
|
||||
blobLimit
|
||||
|
@ -6,6 +6,7 @@ query workspaceQuota($id: String!) {
|
||||
storageQuota
|
||||
historyPeriod
|
||||
memberLimit
|
||||
memberCount
|
||||
humanReadable {
|
||||
name
|
||||
blobLimit
|
||||
|
@ -901,6 +901,7 @@ export interface QuotaQueryType {
|
||||
copilotActionLimit: Maybe<Scalars['SafeInt']['output']>;
|
||||
historyPeriod: Scalars['SafeInt']['output'];
|
||||
humanReadable: HumanReadableQuotaType;
|
||||
memberCount: Scalars['SafeInt']['output'];
|
||||
memberLimit: Scalars['SafeInt']['output'];
|
||||
name: Scalars['String']['output'];
|
||||
storageQuota: Scalars['SafeInt']['output'];
|
||||
@ -2423,6 +2424,7 @@ export type WorkspaceQuotaQuery = {
|
||||
storageQuota: number;
|
||||
historyPeriod: number;
|
||||
memberLimit: number;
|
||||
memberCount: number;
|
||||
usedSize: number;
|
||||
humanReadable: {
|
||||
__typename?: 'HumanReadableQuotaType';
|
||||
|
Loading…
Reference in New Issue
Block a user