mirror of
https://github.com/colanode/colanode.git
synced 2025-12-29 00:25:03 +01:00
Add database authorization checks
This commit is contained in:
@@ -7,7 +7,7 @@ import {
|
||||
RecordNode,
|
||||
} from '@colanode/core';
|
||||
import { compareString, isStringArray } from '@/lib/utils';
|
||||
import { generateNodeIndex } from './nodes';
|
||||
import { generateNodeIndex } from '@/lib/nodes';
|
||||
|
||||
export const getDefaultFieldWidth = (type: FieldType): number => {
|
||||
if (!type) return 0;
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { DatabaseNode } from '@colanode/core';
|
||||
import { DatabaseNode, NodeRole } from '@colanode/core';
|
||||
import { DatabaseViews } from '@/renderer/components/databases/database-views';
|
||||
import { Database } from '@/renderer/components/databases/database';
|
||||
|
||||
interface DatabaseBodyProps {
|
||||
database: DatabaseNode;
|
||||
role: NodeRole;
|
||||
}
|
||||
|
||||
export const DatabaseBody = ({ database }: DatabaseBodyProps) => {
|
||||
export const DatabaseBody = ({ database, role }: DatabaseBodyProps) => {
|
||||
return (
|
||||
<Database databaseId={database.id}>
|
||||
<Database database={database} role={role}>
|
||||
<DatabaseViews />
|
||||
</Database>
|
||||
);
|
||||
|
||||
@@ -31,7 +31,7 @@ export const DatabaseContainer = ({ nodeId }: DatabaseContainerProps) => {
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col">
|
||||
<DatabaseHeader nodes={nodes} database={database} role={role} />
|
||||
<DatabaseBody database={database} />
|
||||
<DatabaseBody database={database} role={role} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,44 +1,45 @@
|
||||
import React from 'react';
|
||||
import { DatabaseContext } from '@/renderer/contexts/database';
|
||||
import { useQuery } from '@/renderer/hooks/use-query';
|
||||
import { useWorkspace } from '@/renderer/contexts/workspace';
|
||||
import { useMutation } from '@/renderer/hooks/use-mutation';
|
||||
import {
|
||||
DatabaseNode,
|
||||
hasCollaboratorAccess,
|
||||
hasEditorAccess,
|
||||
NodeRole,
|
||||
} from '@colanode/core';
|
||||
|
||||
interface DatabaseProps {
|
||||
databaseId: string;
|
||||
database: DatabaseNode;
|
||||
role: NodeRole;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Database = ({ databaseId, children }: DatabaseProps) => {
|
||||
export const Database = ({ database, role, children }: DatabaseProps) => {
|
||||
const workspace = useWorkspace();
|
||||
const { data, isPending } = useQuery({
|
||||
type: 'node_get',
|
||||
nodeId: databaseId,
|
||||
userId: workspace.userId,
|
||||
});
|
||||
const { mutate } = useMutation();
|
||||
|
||||
const { mutate, isPending: isMutating } = useMutation();
|
||||
|
||||
if (isPending || isMutating || !data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (data.type !== 'database') {
|
||||
return null;
|
||||
}
|
||||
const canEdit = hasEditorAccess(role);
|
||||
const canCreateRecord = hasCollaboratorAccess(role);
|
||||
|
||||
return (
|
||||
<DatabaseContext.Provider
|
||||
value={{
|
||||
id: data.id,
|
||||
name: data.attributes.name,
|
||||
fields: Object.values(data.attributes.fields),
|
||||
views: Object.values(data.attributes.views),
|
||||
id: database.id,
|
||||
name: database.attributes.name,
|
||||
fields: Object.values(database.attributes.fields),
|
||||
views: Object.values(database.attributes.views),
|
||||
canEdit,
|
||||
canCreateRecord,
|
||||
createField: (type, name) => {
|
||||
if (!canEdit) {
|
||||
return;
|
||||
}
|
||||
|
||||
mutate({
|
||||
input: {
|
||||
type: 'field_create',
|
||||
databaseId: data.id,
|
||||
databaseId: database.id,
|
||||
name,
|
||||
fieldType: type,
|
||||
userId: workspace.userId,
|
||||
@@ -46,10 +47,14 @@ export const Database = ({ databaseId, children }: DatabaseProps) => {
|
||||
});
|
||||
},
|
||||
renameField: (id, name) => {
|
||||
if (!canEdit) {
|
||||
return;
|
||||
}
|
||||
|
||||
mutate({
|
||||
input: {
|
||||
type: 'node_attribute_set',
|
||||
nodeId: data.id,
|
||||
nodeId: database.id,
|
||||
path: `fields.${id}.name`,
|
||||
value: name,
|
||||
userId: workspace.userId,
|
||||
@@ -57,20 +62,28 @@ export const Database = ({ databaseId, children }: DatabaseProps) => {
|
||||
});
|
||||
},
|
||||
deleteField: (id) => {
|
||||
if (!canEdit) {
|
||||
return;
|
||||
}
|
||||
|
||||
mutate({
|
||||
input: {
|
||||
type: 'node_attribute_delete',
|
||||
nodeId: data.id,
|
||||
nodeId: database.id,
|
||||
path: `fields.${id}`,
|
||||
userId: workspace.userId,
|
||||
},
|
||||
});
|
||||
},
|
||||
createSelectOption: (fieldId, name, color) => {
|
||||
if (!canEdit) {
|
||||
return;
|
||||
}
|
||||
|
||||
mutate({
|
||||
input: {
|
||||
type: 'select_option_create',
|
||||
databaseId: data.id,
|
||||
databaseId: database.id,
|
||||
fieldId,
|
||||
name,
|
||||
color,
|
||||
@@ -79,10 +92,14 @@ export const Database = ({ databaseId, children }: DatabaseProps) => {
|
||||
});
|
||||
},
|
||||
updateSelectOption: (fieldId, attributes) => {
|
||||
if (!canEdit) {
|
||||
return;
|
||||
}
|
||||
|
||||
mutate({
|
||||
input: {
|
||||
type: 'node_attribute_set',
|
||||
nodeId: data.id,
|
||||
nodeId: database.id,
|
||||
path: `fields.${fieldId}.options.${attributes.id}`,
|
||||
value: attributes,
|
||||
userId: workspace.userId,
|
||||
@@ -90,10 +107,14 @@ export const Database = ({ databaseId, children }: DatabaseProps) => {
|
||||
});
|
||||
},
|
||||
deleteSelectOption: (fieldId, optionId) => {
|
||||
if (!canEdit) {
|
||||
return;
|
||||
}
|
||||
|
||||
mutate({
|
||||
input: {
|
||||
type: 'node_attribute_delete',
|
||||
nodeId: data.id,
|
||||
nodeId: database.id,
|
||||
path: `fields.${fieldId}.options.${optionId}`,
|
||||
userId: workspace.userId,
|
||||
},
|
||||
|
||||
@@ -83,6 +83,10 @@ export const FieldCreatePopover = () => {
|
||||
});
|
||||
};
|
||||
|
||||
if (!database.canEdit) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen} modal={true}>
|
||||
<PopoverTrigger>
|
||||
|
||||
@@ -13,6 +13,7 @@ export const FieldRenameInput = ({ field }: FieldRenameInputProps) => {
|
||||
<div className="w-full p-1">
|
||||
<SmartTextInput
|
||||
value={field.name}
|
||||
readOnly={!database.canEdit}
|
||||
onChange={(newName) => {
|
||||
if (newName === field.name) return;
|
||||
database.renameField(field.id, newName);
|
||||
|
||||
@@ -14,6 +14,7 @@ import { useView } from '@/renderer/contexts/view';
|
||||
import { FieldIcon } from '@/renderer/components/databases/fields/field-icon';
|
||||
import { ArrowDownAz, ArrowDownZa, EyeOff, Filter, Trash2 } from 'lucide-react';
|
||||
import { ViewField } from '@/types/databases';
|
||||
import { useDatabase } from '@/renderer/contexts/database';
|
||||
|
||||
interface TableViewFieldHeaderProps {
|
||||
viewField: ViewField;
|
||||
@@ -22,17 +23,15 @@ interface TableViewFieldHeaderProps {
|
||||
export const TableViewFieldHeader = ({
|
||||
viewField,
|
||||
}: TableViewFieldHeaderProps) => {
|
||||
const database = useDatabase();
|
||||
const view = useView();
|
||||
|
||||
const canEditDatabase = true;
|
||||
const canEditView = true;
|
||||
|
||||
const [showDeleteDialog, setShowDeleteDialog] = React.useState(false);
|
||||
|
||||
const [, dragRef] = useDrag<ViewField>({
|
||||
type: 'table-field-header',
|
||||
item: viewField,
|
||||
canDrag: () => canEditView,
|
||||
canDrag: () => database.canEdit,
|
||||
end: (_item, monitor) => {
|
||||
const dropResult = monitor.getDropResult<{ after: string }>();
|
||||
if (!dropResult?.after) return;
|
||||
@@ -76,7 +75,7 @@ export const TableViewFieldHeader = ({
|
||||
bottomLeft: false,
|
||||
bottomRight: false,
|
||||
left: false,
|
||||
right: canEditView,
|
||||
right: database.canEdit,
|
||||
top: false,
|
||||
topLeft: false,
|
||||
topRight: false,
|
||||
@@ -151,7 +150,7 @@ export const TableViewFieldHeader = ({
|
||||
<span>Filter</span>
|
||||
</div>
|
||||
<Separator />
|
||||
{canEditView && (
|
||||
{database.canEdit && (
|
||||
<div
|
||||
className="flex cursor-pointer flex-row items-center gap-2 p-1 hover:bg-gray-100"
|
||||
onClick={() => {
|
||||
@@ -162,7 +161,7 @@ export const TableViewFieldHeader = ({
|
||||
<span>Hide in view</span>
|
||||
</div>
|
||||
)}
|
||||
{canEditDatabase && (
|
||||
{database.canEdit && (
|
||||
<div
|
||||
className="flex cursor-pointer flex-row items-center gap-2 p-1 hover:bg-gray-100"
|
||||
onClick={() => {
|
||||
|
||||
@@ -2,8 +2,10 @@ import { useView } from '@/renderer/contexts/view';
|
||||
import { TableViewNameHeader } from '@/renderer/components/databases/tables/table-view-name-header';
|
||||
import { TableViewFieldHeader } from '@/renderer/components/databases/tables/table-view-field-header';
|
||||
import { FieldCreatePopover } from '@/renderer/components/databases/fields/field-create-popover';
|
||||
import { useDatabase } from '@/renderer/contexts/database';
|
||||
|
||||
export const TableViewHeader = () => {
|
||||
const database = useDatabase();
|
||||
const view = useView();
|
||||
|
||||
return (
|
||||
@@ -13,7 +15,7 @@ export const TableViewHeader = () => {
|
||||
{view.fields.map((field) => {
|
||||
return <TableViewFieldHeader viewField={field} key={field.field.id} />;
|
||||
})}
|
||||
<FieldCreatePopover />
|
||||
{database.canEdit && <FieldCreatePopover />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from 'react';
|
||||
import { Resizable } from 're-resizable';
|
||||
import {
|
||||
Popover,
|
||||
@@ -10,12 +11,12 @@ import { useView } from '@/renderer/contexts/view';
|
||||
import { useDrop } from 'react-dnd';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { ArrowDownAz, ArrowDownZa, Filter, Type } from 'lucide-react';
|
||||
import { useDatabase } from '@/renderer/contexts/database';
|
||||
|
||||
export const TableViewNameHeader = () => {
|
||||
const database = useDatabase();
|
||||
const view = useView();
|
||||
|
||||
const canEditView = true;
|
||||
|
||||
const [dropMonitor, dropRef] = useDrop({
|
||||
accept: 'table-field-header',
|
||||
drop: () => ({
|
||||
@@ -41,7 +42,7 @@ export const TableViewNameHeader = () => {
|
||||
bottomLeft: false,
|
||||
bottomRight: false,
|
||||
left: false,
|
||||
right: canEditView,
|
||||
right: database.canEdit,
|
||||
top: false,
|
||||
topLeft: false,
|
||||
topRight: false,
|
||||
@@ -86,8 +87,8 @@ export const TableViewNameHeader = () => {
|
||||
/>
|
||||
</div>
|
||||
<Separator />
|
||||
{true && (
|
||||
<>
|
||||
{database.canEdit && (
|
||||
<React.Fragment>
|
||||
<div
|
||||
className="flex cursor-pointer flex-row items-center gap-2 p-1 hover:bg-gray-100"
|
||||
onClick={() => {
|
||||
@@ -117,7 +118,7 @@ export const TableViewNameHeader = () => {
|
||||
<ArrowDownZa className="size-4" />
|
||||
<span>Sort descending</span>
|
||||
</div>
|
||||
</>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<div className="flex cursor-pointer flex-row items-center gap-2 p-1 hover:bg-gray-100">
|
||||
<Filter className="size-4" />
|
||||
|
||||
@@ -8,6 +8,10 @@ export const TableViewRecordCreateRow = () => {
|
||||
const database = useDatabase();
|
||||
const { mutate, isPending } = useMutation();
|
||||
|
||||
if (!database.canCreateRecord) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -35,10 +35,6 @@ export const TableViewSettingsPopover = () => {
|
||||
const [openDelete, setOpenDelete] = React.useState(false);
|
||||
const [deleteFieldId, setDeleteFieldId] = React.useState<string | null>(null);
|
||||
|
||||
const canEditDatabase = true;
|
||||
const canEditView = true;
|
||||
const canDeleteView = true;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
@@ -49,22 +45,33 @@ export const TableViewSettingsPopover = () => {
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="mr-4 flex w-[600px] flex-col gap-1.5 p-2">
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<AvatarPopover
|
||||
onPick={(avatar) => {
|
||||
if (isPending) return;
|
||||
if (avatar === view.avatar) return;
|
||||
{database.canEdit ? (
|
||||
<AvatarPopover
|
||||
onPick={(avatar) => {
|
||||
if (isPending) return;
|
||||
if (avatar === view.avatar) return;
|
||||
|
||||
mutate({
|
||||
input: {
|
||||
type: 'node_attribute_set',
|
||||
nodeId: view.id,
|
||||
path: 'avatar',
|
||||
value: avatar,
|
||||
userId: workspace.userId,
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
mutate({
|
||||
input: {
|
||||
type: 'node_attribute_set',
|
||||
nodeId: view.id,
|
||||
path: 'avatar',
|
||||
value: avatar,
|
||||
userId: workspace.userId,
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Button type="button" variant="outline" size="icon">
|
||||
<Avatar
|
||||
id={view.id}
|
||||
name={view.name}
|
||||
avatar={view.avatar}
|
||||
className="h-6 w-6"
|
||||
/>
|
||||
</Button>
|
||||
</AvatarPopover>
|
||||
) : (
|
||||
<Button type="button" variant="outline" size="icon">
|
||||
<Avatar
|
||||
id={view.id}
|
||||
@@ -73,9 +80,10 @@ export const TableViewSettingsPopover = () => {
|
||||
className="h-6 w-6"
|
||||
/>
|
||||
</Button>
|
||||
</AvatarPopover>
|
||||
)}
|
||||
<SmartTextInput
|
||||
value={view.name}
|
||||
readOnly={!database.canEdit}
|
||||
onChange={(newName) => {
|
||||
if (isPending) return;
|
||||
if (newName === view.name) return;
|
||||
@@ -117,10 +125,10 @@ export const TableViewSettingsPopover = () => {
|
||||
<TooltipTrigger>
|
||||
<span
|
||||
className={cn(
|
||||
canEditView ? 'cursor-pointer' : 'opacity-50'
|
||||
database.canEdit ? 'cursor-pointer' : 'opacity-50'
|
||||
)}
|
||||
onClick={() => {
|
||||
if (!canEditView) return;
|
||||
if (!database.canEdit) return;
|
||||
|
||||
view.setFieldDisplay(field.id, !isHidden);
|
||||
}}
|
||||
@@ -138,13 +146,13 @@ export const TableViewSettingsPopover = () => {
|
||||
: 'Hide field from this view'}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
{canEditDatabase && (
|
||||
{database.canEdit && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Trash2
|
||||
className={cn(
|
||||
'size-4',
|
||||
canEditView ? 'cursor-pointer' : 'opacity-50'
|
||||
database.canEdit ? 'cursor-pointer' : 'opacity-50'
|
||||
)}
|
||||
onClick={() => {
|
||||
setDeleteFieldId(field.id);
|
||||
@@ -162,7 +170,7 @@ export const TableViewSettingsPopover = () => {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{canEditView && canDeleteView && (
|
||||
{database.canEdit && (
|
||||
<React.Fragment>
|
||||
<Separator />
|
||||
<div className="flex flex-col gap-2 text-sm">
|
||||
|
||||
@@ -129,6 +129,10 @@ export const ViewCreateDialog = ({
|
||||
});
|
||||
};
|
||||
|
||||
if (!database.canEdit) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent>
|
||||
|
||||
@@ -27,6 +27,10 @@ export const ViewDeleteDialog = ({
|
||||
const database = useDatabase();
|
||||
const { mutate, isPending } = useMutation();
|
||||
|
||||
if (!database.canEdit) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<AlertDialog open={open} onOpenChange={onOpenChange}>
|
||||
<AlertDialogContent>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { ViewTab } from '@/renderer/components/databases/view-tab';
|
||||
import { ViewCreateButton } from '@/renderer/components/databases/view-create-button';
|
||||
import { useDatabaseViews } from '@/renderer/contexts/database-views';
|
||||
import { useDatabase } from '@/renderer/contexts/database';
|
||||
|
||||
export const ViewTabs = () => {
|
||||
const database = useDatabase();
|
||||
const databaseViews = useDatabaseViews();
|
||||
|
||||
return (
|
||||
@@ -15,7 +17,7 @@ export const ViewTabs = () => {
|
||||
onClick={() => databaseViews.setActiveViewId(view.id)}
|
||||
/>
|
||||
))}
|
||||
<ViewCreateButton />
|
||||
{database.canEdit && <ViewCreateButton />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -67,6 +67,10 @@ export const View = ({ view }: ViewProps) => {
|
||||
isSearchBarOpened,
|
||||
isSortsOpened,
|
||||
setFieldDisplay: (id: string, display: boolean) => {
|
||||
if (!database.canEdit) {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewField = view.fields[id];
|
||||
if (viewField && viewField.display === display) {
|
||||
return;
|
||||
@@ -92,6 +96,10 @@ export const View = ({ view }: ViewProps) => {
|
||||
});
|
||||
},
|
||||
resizeField: (id: string, width: number) => {
|
||||
if (!database.canEdit) {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewField = view.fields[id];
|
||||
if (viewField && viewField.width === width) {
|
||||
return;
|
||||
@@ -117,6 +125,10 @@ export const View = ({ view }: ViewProps) => {
|
||||
});
|
||||
},
|
||||
resizeName: (width: number) => {
|
||||
if (!database.canEdit) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (view.nameWidth === width) {
|
||||
return;
|
||||
}
|
||||
@@ -134,6 +146,10 @@ export const View = ({ view }: ViewProps) => {
|
||||
});
|
||||
},
|
||||
moveField: (id: string, after: string) => {
|
||||
if (!database.canEdit) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newIndex = generateViewFieldIndex(
|
||||
database.fields,
|
||||
Object.values(view.fields),
|
||||
@@ -166,6 +182,10 @@ export const View = ({ view }: ViewProps) => {
|
||||
isFieldFilterOpened: (fieldId: string) =>
|
||||
openedFieldFilters.includes(fieldId),
|
||||
initFieldFilter: (fieldId: string) => {
|
||||
if (!database.canEdit) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (view.filters[fieldId]) {
|
||||
setOpenedFieldFilters((prev) => [...prev, fieldId]);
|
||||
return;
|
||||
@@ -202,6 +222,10 @@ export const View = ({ view }: ViewProps) => {
|
||||
});
|
||||
},
|
||||
updateFilter: (id: string, filter: ViewFilterAttributes) => {
|
||||
if (!database.canEdit) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!view.filters[id]) {
|
||||
return;
|
||||
}
|
||||
@@ -222,6 +246,10 @@ export const View = ({ view }: ViewProps) => {
|
||||
});
|
||||
},
|
||||
removeFilter: (id: string) => {
|
||||
if (!database.canEdit) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!view.filters[id]) {
|
||||
return;
|
||||
}
|
||||
@@ -242,6 +270,10 @@ export const View = ({ view }: ViewProps) => {
|
||||
});
|
||||
},
|
||||
initFieldSort: (fieldId: string) => {
|
||||
if (!database.canEdit) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (view.sorts[fieldId]) {
|
||||
return;
|
||||
}
|
||||
@@ -274,6 +306,10 @@ export const View = ({ view }: ViewProps) => {
|
||||
});
|
||||
},
|
||||
updateSort: (id: string, sort: ViewSortAttributes) => {
|
||||
if (!database.canEdit) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!view.sorts[id]) {
|
||||
return;
|
||||
}
|
||||
@@ -295,6 +331,10 @@ export const View = ({ view }: ViewProps) => {
|
||||
});
|
||||
},
|
||||
removeSort: (id: string) => {
|
||||
if (!database.canEdit) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!view.sorts[id]) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4,18 +4,30 @@ import { ScrollArea } from '@/renderer/components/ui/scroll-area';
|
||||
import { Document } from '@/renderer/components/documents/document';
|
||||
import { Separator } from '@/renderer/components/ui/separator';
|
||||
import { RecordProvider } from '@/renderer/components/records/record-provider';
|
||||
import { hasEditorAccess, NodeRole, RecordNode } from '@colanode/core';
|
||||
import {
|
||||
DatabaseNode,
|
||||
hasEditorAccess,
|
||||
NodeRole,
|
||||
RecordNode,
|
||||
} from '@colanode/core';
|
||||
|
||||
interface RecordBodyProps {
|
||||
record: RecordNode;
|
||||
role: NodeRole;
|
||||
recordRole: NodeRole;
|
||||
database: DatabaseNode;
|
||||
databaseRole: NodeRole;
|
||||
}
|
||||
|
||||
export const RecordBody = ({ record, role }: RecordBodyProps) => {
|
||||
const canEdit = hasEditorAccess(role);
|
||||
export const RecordBody = ({
|
||||
record,
|
||||
recordRole,
|
||||
database,
|
||||
databaseRole,
|
||||
}: RecordBodyProps) => {
|
||||
const canEdit = hasEditorAccess(recordRole);
|
||||
|
||||
return (
|
||||
<Database databaseId={record.attributes.databaseId}>
|
||||
<Database database={database} role={databaseRole}>
|
||||
<ScrollArea className="h-full max-h-full w-full overflow-y-auto px-10 pb-12">
|
||||
<RecordProvider record={record}>
|
||||
<RecordAttributes />
|
||||
|
||||
@@ -22,16 +22,40 @@ export const RecordContainer = ({ nodeId }: RecordContainerProps) => {
|
||||
|
||||
const nodes = data ?? [];
|
||||
const record = nodes.find((node) => node.id === nodeId);
|
||||
const role = extractNodeRole(nodes, workspace.userId);
|
||||
if (!record || record.type !== 'record') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!record || record.type !== 'record' || !role) {
|
||||
const databaseIndex = nodes.findIndex(
|
||||
(node) => node.id === record.attributes.databaseId
|
||||
);
|
||||
if (databaseIndex === -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const database = nodes[databaseIndex];
|
||||
if (!database || database.type !== 'database') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const databaseAncestors = nodes.slice(0, databaseIndex);
|
||||
|
||||
const recordRole = extractNodeRole(nodes, workspace.userId);
|
||||
const databaseRole = extractNodeRole(databaseAncestors, workspace.userId);
|
||||
|
||||
if (!recordRole || !databaseRole) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col">
|
||||
<RecordHeader nodes={nodes} record={record} role={role} />
|
||||
<RecordBody record={record} role={role} />
|
||||
<RecordHeader nodes={nodes} record={record} role={recordRole} />
|
||||
<RecordBody
|
||||
record={record}
|
||||
recordRole={recordRole}
|
||||
database={database}
|
||||
databaseRole={databaseRole}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -11,6 +11,8 @@ interface DatabaseContext {
|
||||
name: string;
|
||||
fields: FieldAttributes[];
|
||||
views: ViewAttributes[];
|
||||
canEdit: boolean;
|
||||
canCreateRecord: boolean;
|
||||
createField: (type: FieldType, name: string) => void;
|
||||
renameField: (id: string, name: string) => void;
|
||||
deleteField: (id: string) => void;
|
||||
|
||||
Reference in New Issue
Block a user