Skip to content

Commit

Permalink
feat(core): guard service (#9816)
Browse files Browse the repository at this point in the history
  • Loading branch information
EYHN authored Feb 9, 2025
1 parent 879157b commit 92f4f0c
Show file tree
Hide file tree
Showing 89 changed files with 1,519 additions and 521 deletions.
4 changes: 2 additions & 2 deletions packages/backend/server/src/core/permission/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ export class PermissionModule {}
export { PermissionService } from './service';
export {
DOC_ACTIONS,
type DocActionPermissions,
type DocAction,
DocRole,
fixupDocRole,
mapDocRoleToPermissions,
mapWorkspaceRoleToPermissions,
PublicDocMode,
WORKSPACE_ACTIONS,
type WorkspaceActionPermissions,
type WorkspaceAction,
WorkspaceRole,
} from './types';
6 changes: 0 additions & 6 deletions packages/backend/server/src/core/permission/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,6 @@ type ResourceActionName<T extends keyof typeof Actions> =
export type WorkspaceAction = ResourceActionName<'Workspace'>;
export type DocAction = ResourceActionName<'Doc'>;
export type Action = WorkspaceAction | DocAction;
export type WorkspaceActionPermissions = {
[key in WorkspaceAction]: boolean;
};
export type DocActionPermissions = {
[key in DocAction]: boolean;
};

const cache = new WeakMap<object, any>();
const buildPathReader = (
Expand Down
29 changes: 11 additions & 18 deletions packages/backend/server/src/core/workspaces/resolvers/doc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { Models } from '../../../models';
import { CurrentUser } from '../../auth';
import {
DOC_ACTIONS,
type DocActionPermissions,
DocAction,
DocRole,
fixupDocRole,
mapDocRoleToPermissions,
Expand All @@ -41,6 +41,7 @@ import {
import { PublicUserType } from '../../user';
import { DocID } from '../../utils/doc';
import { WorkspaceType } from '../types';
import { DotToUnderline, mapPermissionToGraphqlPermissions } from './workspace';

registerEnumType(PublicDocMode, {
name: 'PublicDocMode',
Expand Down Expand Up @@ -128,10 +129,12 @@ class GrantedDocUserType {
@ObjectType()
class PaginatedGrantedDocUserType extends Paginated(GrantedDocUserType) {}

const DocPermissions = registerObjectType<DocActionPermissions>(
const DocPermissions = registerObjectType<
Record<DotToUnderline<DocAction>, boolean>
>(
Object.fromEntries(
DOC_ACTIONS.map(action => [
action,
action.replaceAll('.', '_'),
{
type: () => Boolean,
options: {
Expand All @@ -143,15 +146,6 @@ const DocPermissions = registerObjectType<DocActionPermissions>(
{ name: 'DocPermissions' }
);

@ObjectType()
export class DocRolePermissions {
@Field(() => DocRole)
role!: DocRole;

@Field(() => DocPermissions)
permissions!: DocActionPermissions;
}

@Resolver(() => WorkspaceType)
export class WorkspaceDocResolver {
private readonly logger = new Logger(WorkspaceDocResolver.name);
Expand Down Expand Up @@ -365,7 +359,7 @@ export class DocResolver {
async permissions(
@CurrentUser() user: CurrentUser,
@Parent() doc: DocType
): Promise<DocRolePermissions> {
): Promise<InstanceType<typeof DocPermissions>> {
const [permission, workspacePermission] = await this.prisma.$transaction(
tx =>
Promise.all([
Expand All @@ -385,12 +379,11 @@ export class DocResolver {
])
);

return {
role: permission?.type ?? DocRole.External,
permissions: mapDocRoleToPermissions(
return mapPermissionToGraphqlPermissions(
mapDocRoleToPermissions(
fixupDocRole(workspacePermission?.type, permission?.type)
),
};
)
);
}

@ResolveField(() => PaginatedGrantedDocUserType, {
Expand Down
32 changes: 26 additions & 6 deletions packages/backend/server/src/core/workspaces/resolvers/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import {
mapWorkspaceRoleToPermissions,
PermissionService,
WORKSPACE_ACTIONS,
type WorkspaceActionPermissions,
WorkspaceAction,
WorkspaceRole,
} from '../../permission';
import { QuotaService, WorkspaceQuotaType } from '../../quota';
Expand All @@ -51,6 +51,22 @@ import {
} from '../types';
import { WorkspaceService } from './service';

export type DotToUnderline<T extends string> =
T extends `${infer Prefix}.${infer Suffix}`
? `${Prefix}_${DotToUnderline<Suffix>}`
: T;

export function mapPermissionToGraphqlPermissions<A extends string>(
permission: Record<A, boolean>
): Record<DotToUnderline<A>, boolean> {
return Object.fromEntries(
Object.entries(permission).map(([key, value]) => [
key.replaceAll('.', '_'),
value,
])
) as Record<DotToUnderline<A>, boolean>;
}

@ObjectType()
export class EditorType implements Partial<Editor> {
@Field()
Expand All @@ -75,10 +91,12 @@ class WorkspacePageMeta {
updatedBy!: EditorType | null;
}

const WorkspacePermissions = registerObjectType<WorkspaceActionPermissions>(
const WorkspacePermissions = registerObjectType<
Record<DotToUnderline<WorkspaceAction>, boolean>
>(
Object.fromEntries(
WORKSPACE_ACTIONS.map(action => [
action,
action.replaceAll('.', '_'),
{
type: () => Boolean,
options: {
Expand All @@ -96,7 +114,7 @@ export class WorkspaceRolePermissions {
role!: WorkspaceRole;

@Field(() => WorkspacePermissions)
permissions!: WorkspaceActionPermissions;
permissions!: Record<DotToUnderline<WorkspaceAction>, boolean>;
}

/**
Expand Down Expand Up @@ -342,7 +360,7 @@ export class WorkspaceResolver {
async workspaceRolePermissions(
@CurrentUser() user: CurrentUser,
@Args('id') id: string
) {
): Promise<WorkspaceRolePermissions> {
const workspace = await this.prisma.workspaceUserPermission.findFirst({
where: { workspaceId: id, userId: user.id },
});
Expand All @@ -351,7 +369,9 @@ export class WorkspaceResolver {
}
return {
role: workspace.type,
permissions: mapWorkspaceRoleToPermissions(workspace.type),
permissions: mapPermissionToGraphqlPermissions(
mapWorkspaceRoleToPermissions(workspace.type)
),
};
}

Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/component/src/theme/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ summary {
}

a,
button {
button:not([disabled]) {
cursor: pointer;
}

Expand Down
5 changes: 4 additions & 1 deletion packages/frontend/component/src/ui/button/button.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ export const button = style({
outline: 0,
borderRadius: 8,
transition: 'all .3s',
cursor: 'pointer',
['WebkitAppRegion' as string]: 'no-drag',

// hover layer
Expand Down Expand Up @@ -169,6 +168,10 @@ export const button = style({
opacity: 0.5,
},

'&:not([data-disabled])': {
cursor: 'pointer',
},

// default keyboard focus style
'&:focus-visible::after': {
content: '""',
Expand Down
12 changes: 6 additions & 6 deletions packages/frontend/component/src/ui/dnd/drop-target.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,11 @@ export const useDropTarget = <D extends DNDData = DNDData>(

// external data is only available in drop event thus
// this is the only case for getAdaptedEventArgs
const args = getAdaptedEventArgs(_args, options.fromExternalData, true);
const args = {
...getAdaptedEventArgs(_args, options.fromExternalData, true),
treeInstruction: extractInstruction(_args.self.data),
closestEdge: extractClosestEdge(_args.self.data),
};
if (
isExternalDrag(_args) &&
options.fromExternalData &&
Expand All @@ -329,11 +333,7 @@ export const useDropTarget = <D extends DNDData = DNDData>(
}

if (args.location.current.dropTargets[0]?.element === element) {
options.onDrop?.({
...args,
treeInstruction: extractInstruction(args.self.data),
closestEdge: extractClosestEdge(args.self.data),
} as DropTargetDropEvent<D>);
options.onDrop?.(args as DropTargetDropEvent<D>);
}
},
getData: (args: DropTargetGetFeedback<D>) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/component/src/ui/menu/styles.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export const menuItem = style({
'&.block': {
maxWidth: '100%',
},
'&[data-disabled]': {
'&[data-disabled], &.disabled': {
vars: {
[iconColor]: cssVarV2('icon/disable'),
[labelColor]: cssVarV2('text/secondary'),
Expand Down
6 changes: 4 additions & 2 deletions packages/frontend/component/src/ui/menu/use-menu-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ export const useMenuItem = <T extends MenuItemProps>({
checked,
selected,
block,
disabled,
...otherProps
}: T) => {
const className = clsx(
styles.menuItem,
{
danger: type === 'danger',
warning: type === 'warning',
danger: disabled ? false : type === 'danger',
warning: disabled ? false : type === 'warning',
disabled,
checked,
selected,
block,
Expand Down
10 changes: 6 additions & 4 deletions packages/frontend/component/src/ui/property/property.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,12 @@ export const propertyValueContainer = style({
'&[data-readonly="false"][data-hoverable="true"]': {
cursor: 'pointer',
},
'&[data-readonly="false"][data-hoverable="true"]:is(:hover, :focus-within)':
{
backgroundColor: cssVarV2('layer/background/hoverOverlay'),
},
'&[data-readonly="true"][data-hoverable="true"]': {
cursor: 'default',
},
'&[data-hoverable="true"]:is(:hover, :focus-within)': {
backgroundColor: cssVarV2('layer/background/hoverOverlay'),
},
},
});

Expand Down
3 changes: 3 additions & 0 deletions packages/frontend/component/src/ui/radio/radio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export const RadioGroup = memo(function RadioGroup({
indicatorStyle,
iconMode,
onChange,
disabled,
}: RadioProps) {
const animationTImerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const finalItems = useMemo(() => {
Expand Down Expand Up @@ -158,6 +159,7 @@ export const RadioGroup = memo(function RadioGroup({
className={clsx(styles.radioButtonGroup, className)}
style={finalStyle}
data-icon-mode={iconMode}
disabled={disabled}
>
{finalItems.map(({ customRender, ...item }, index) => {
const testId = item.testId ? { 'data-testid': item.testId } : {};
Expand All @@ -179,6 +181,7 @@ export const RadioGroup = memo(function RadioGroup({
style={style}
{...testId}
{...item.attrs}
disabled={disabled}
>
<RadixRadioGroup.Indicator
forceMount
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/component/src/ui/radio/styles.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const radioButton = style({
'&[data-state="checked"]': {
color: cssVarV2('switch/fontColor/primary'),
},
'&[data-state="unchecked"]:hover': {
'&[data-state="unchecked"]:hover:not([disabled])': {
background: cssVarV2('switch/buttonBackground/hover'),
},
'[data-icon-mode=true] &': {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ const REPLACE_SELECTION = {
icon: ReplaceIcon,
title: 'Replace selection',
showWhen: (host: EditorHost) => {
if (host.std.store.readonly$.value) {
return false;
}
const textSelection = host.selection.find(TextSelection);
const blockSelections = host.selection.filter(BlockSelection);
if (
Expand Down Expand Up @@ -252,7 +255,12 @@ const REPLACE_SELECTION = {
const INSERT_BELOW = {
icon: InsertBelowIcon,
title: 'Insert below',
showWhen: () => true,
showWhen: (host: EditorHost) => {
if (host.std.store.readonly$.value) {
return false;
}
return true;
},
toast: 'Successfully inserted',
handler: async (
host: EditorHost,
Expand Down Expand Up @@ -282,7 +290,12 @@ const SAVE_CHAT_TO_BLOCK_ACTION: ChatAction = {
icon: BlockIcon,
title: 'Save chat to block',
toast: 'Successfully saved chat to a block',
showWhen: () => true,
showWhen: (host: EditorHost) => {
if (host.std.store.readonly$.value) {
return false;
}
return true;
},
handler: async (
host: EditorHost,
_,
Expand Down Expand Up @@ -378,7 +391,12 @@ const SAVE_CHAT_TO_BLOCK_ACTION: ChatAction = {
const ADD_TO_EDGELESS_AS_NOTE = {
icon: CreateIcon,
title: 'Add to edgeless as note',
showWhen: () => true,
showWhen: (host: EditorHost) => {
if (host.std.store.readonly$.value) {
return false;
}
return true;
},
toast: 'New note created',
handler: async (host: EditorHost, content: string) => {
reportResponse('result:add-note');
Expand Down Expand Up @@ -451,7 +469,12 @@ const CREATE_AS_DOC = {
const CREATE_AS_LINKED_DOC = {
icon: CreateIcon,
title: 'Create as a linked doc',
showWhen: () => true,
showWhen: (host: EditorHost) => {
if (host.std.store.readonly$.value) {
return false;
}
return true;
},
toast: 'New doc created',
handler: async (host: EditorHost, content: string) => {
reportResponse('result:add-page');
Expand Down
Loading

0 comments on commit 92f4f0c

Please sign in to comment.