Skip to content

Commit

Permalink
Fixed workspace management on postgres
Browse files Browse the repository at this point in the history
  • Loading branch information
GuilhermeF03 committed May 22, 2024
1 parent 7d31689 commit 8a86046
Show file tree
Hide file tree
Showing 25 changed files with 286 additions and 143 deletions.
6 changes: 3 additions & 3 deletions code/client/src/domain/workspaces/tree/types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { WorkspaceResourceMetadata } from '@notespace/shared/src/workspace/types/resource';
import { WorkspaceResource } from '@notespace/shared/src/workspace/types/resource';

export type TreeNode = {
node: WorkspaceTreeNode;
children: TreeNode[];
};

export type WorkspaceTreeNode = WorkspaceResourceMetadata;
export type WorkspaceTreeNode = WorkspaceResource;

export type WorkspaceTreeNodes = Map<string, WorkspaceTreeNode>;
export type WorkspaceTreeNodes = Record<string, WorkspaceTreeNode>;
69 changes: 34 additions & 35 deletions code/client/src/domain/workspaces/tree/useWorkspaceTree.ts
Original file line number Diff line number Diff line change
@@ -1,74 +1,73 @@
import { useState } from 'react';
import { rootNode } from '@domain/workspaces/tree/utils';
import { WorkspaceTreeNode } from '@domain/workspaces/tree/types';
import { WorkspaceTreeNode, WorkspaceTreeNodes } from '@domain/workspaces/tree/types';
import { WorkspaceResources } from '@notespace/shared/src/workspace/types/workspace';

function useWorkspaceTree() {
const [nodes, setNodes] = useState<Map<string, WorkspaceTreeNode>>(new Map());

function setTree(nodes: WorkspaceTreeNode[]) {
const nodesMap = new Map(nodes.map(node => [node.id, node]));
const root = rootNode(
Array.from(nodes?.values() || [])
.filter(node => node.parent === 'root')
.map(node => node.id)
);
nodesMap.set('root', root);
setNodes(nodesMap);
}
const [nodes, setNodes] = useState<WorkspaceTreeNodes>({});

function getNode(id: string) {
return nodes.get(id);
function setTree(nodes: WorkspaceResources) {
const wid = Object.values(nodes)[0].workspace;
const newNodes = { ...nodes };
newNodes[wid] = rootNode(newNodes[wid] ? newNodes[wid].children : []);
setNodes(newNodes);
}

const getNode = (id: string) => nodes[id];

function addNode(node: WorkspaceTreeNode) {
const newNodes = new Map(nodes);
const parentNode = newNodes.get(node.parent);
const newNodes = { ...nodes };
const parentNode = newNodes[node.parent];
if (!parentNode) throw new Error('Invalid parent id: ' + node.parent);
newNodes.set(node.id, node);
newNodes.set(node.parent, { ...parentNode, children: [...parentNode.children, node.id] });
newNodes[node.id] = node;
newNodes[node.parent] = { ...parentNode, children: [...parentNode.children, node.id] };
setNodes(newNodes);
}

function removeNode(id: string) {
const node = nodes.get(id);
const node = nodes[id];

if (!node) throw new Error('Invalid id: ' + id);
const { parent } = node;
const parentNode = nodes.get(parent);
const parentNode = nodes[parent];

if (!parentNode) throw new Error('Invalid parent id: ' + parent);
const newNodes = new Map(nodes);
const newNodes = { ...nodes };
const index = parentNode.children.indexOf(id);

if (index !== -1) parentNode.children.splice(index, 1);
newNodes.delete(id);
newNodes.set(parent, parentNode);

delete newNodes[id];

newNodes[parent] = parentNode;
setNodes(newNodes);
}

function updateNode(id: string, name: string) {
const node = nodes.get(id);
const node = nodes[id];
if (!node) throw new Error('Invalid id: ' + id);
const newNode = { ...node, name };
nodes.set(id, newNode);
setNodes(new Map(nodes));
nodes[id] = { ...node, name };
setNodes({ ...nodes });
}

function moveNode(id: string, newParent: string) {
const node = nodes.get(id);
const node = nodes[id];
if (!node) throw new Error('Invalid id: ' + id);
const { parent } = node;
const parentNode = nodes.get(parent);
const parentNode = nodes[parent];

if (parentNode) {
const index = parentNode.children.indexOf(node.id);
if (index !== -1) parentNode.children.splice(index, 1);
nodes.set(parent, parentNode);
nodes[parent] = parentNode;
}
const newParentNode = nodes.get(newParent);
const newParentNode = nodes[newParent];
if (!newParentNode) throw new Error('Invalid parent id: ' + newParent);
newParentNode.children.push(node.id);
node.parent = newParent;
nodes.set(id, node);
nodes.set(newParent, newParentNode);
setNodes(new Map(nodes));
nodes[id] = node;
nodes[newParent] = newParentNode;
setNodes({ ...nodes });
}

function isDescendant(parentId: string, nodeId: string): boolean {
Expand Down
11 changes: 6 additions & 5 deletions code/client/src/domain/workspaces/tree/utils.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { ResourceType } from '@notespace/shared/src/workspace/types/resource';
import { TreeNode, WorkspaceTreeNode } from '@domain/workspaces/tree/types';
import { TreeNode, WorkspaceTreeNode, WorkspaceTreeNodes } from '@domain/workspaces/tree/types';

export function getTree(nodes: Map<string, WorkspaceTreeNode>, id: string = 'root'): TreeNode {
const root = nodes.get(id)!;
export function getTree(nodes: WorkspaceTreeNodes, id: string): TreeNode {
const root = nodes[id];
return {
node: root,
children: root.children.map(id => getTree(nodes, id)),
};
}

export function rootNode(children?: string[]): WorkspaceTreeNode {
export function rootNode(wid: string, children?: string[]): WorkspaceTreeNode {
return {
id: 'root',
id: wid,
workspace: wid,
name: 'root',
parent: '',
children: children || [],
Expand Down
2 changes: 1 addition & 1 deletion code/client/src/services/resource/resourceService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ function resourceService(http: HttpCommunication, wid: string) {
}

async function createResource(name: string, type: ResourceType, parent?: string): Promise<string> {
const resource: ResourceInputModel = { name, type, parent };
const resource: ResourceInputModel = { name, type, parent: parent || wid };
const { id } = await http.post(`/workspaces/${wid}`, resource);
return id;
}
Expand Down
7 changes: 7 additions & 0 deletions code/client/src/ui/components/sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,18 @@ import './Sidebar.scss';
import WorkspaceTree from '@ui/components/sidebar/components/WorkspaceTree';
import { FaHome } from 'react-icons/fa';
import { IoMdSettings } from 'react-icons/io';
import { useEffect } from 'react';

function Sidebar() {
const { isOpen, isLocked, isLoaded, handleClick, handleMouseEnter, handleMouseLeave } = useSidebarState();
const { workspace, nodes, operations } = useWorkspace();

useEffect(() => {
if (workspace) {
console.log('nodes', nodes);
}
}, [nodes, workspace]);

if (!isLoaded) return null;
return (
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import ResourceView from '@ui/components/sidebar/components/ResourceView';
import { WorkspaceMetaData } from '@notespace/shared/src/workspace/types/workspace';
import { getTree } from '@domain/workspaces/tree/utils';
import { WorkspaceTreeNodes } from '@domain/workspaces/tree/types';
import { ResourceOperationsType } from '@ui/contexts/workspace/WorkspaceContext';
import { ResourceType } from '@notespace/shared/src/workspace/types/resource';
import { DragEvent, useState } from 'react';
import { UseResourcesType } from '@ui/contexts/workspace/useResources';

type WorkspaceTreeProps = {
workspace: WorkspaceMetaData;
operations: ResourceOperationsType;
operations: Omit<UseResourcesType['operations'], 'setResources'>;
nodes?: WorkspaceTreeNodes;
};

Expand All @@ -25,15 +25,15 @@ function WorkspaceTree({ workspace, nodes, operations }: WorkspaceTreeProps) {

async function onDrop(e: DragEvent<HTMLDivElement>) {
if (!dragId) return;
const parentId = e.currentTarget.id || 'root';
const parentId = e.currentTarget.id || workspace.id;
await operations.moveResource(dragId, parentId);
}

return (
<div className="workspace-tree">
<ul>
{nodes &&
getTree(nodes).children.map(node => (
getTree(nodes, workspace.id).children.map(node => (
<li key={node.node.id}>
<ResourceView
workspace={workspace.id}
Expand Down
34 changes: 20 additions & 14 deletions code/client/src/ui/contexts/workspace/WorkspaceContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,14 @@ import { useCommunication } from '@ui/contexts/communication/useCommunication';
import useError from '@ui/contexts/error/useError';
import { useParams } from 'react-router-dom';
import useWorkspaceService from '@services/workspace/useWorkspaceService';
import useResources from '@ui/contexts/workspace/useResources';
import { ResourceType, WorkspaceResource } from '@notespace/shared/src/workspace/types/resource';
import useResources, { UseResourcesType } from '@ui/contexts/workspace/useResources';
import { WorkspaceResource } from '@notespace/shared/src/workspace/types/resource';
import { WorkspaceTreeNodes } from '@domain/workspaces/tree/types';

export type ResourceOperationsType = {
createResource: (name: string, type: ResourceType, parent?: string) => Promise<void>;
deleteResource: (id: string) => Promise<void>;
updateResource: (id: string, newProps: Partial<WorkspaceResource>) => Promise<void>;
moveResource: (id: string, parent: string) => Promise<void>;
};

export type WorkspaceContextType = {
workspace?: WorkspaceMetaData;
resources?: WorkspaceResource[];
operations?: ResourceOperationsType;
operations?: Omit<UseResourcesType['operations'], 'setResources'>;
nodes?: WorkspaceTreeNodes;
};

Expand All @@ -28,7 +21,7 @@ export const WorkspaceContext = createContext<WorkspaceContextType>({});
export function WorkspaceProvider({ children }: { children: React.ReactNode }) {
const services = useWorkspaceService();
const [workspace, setWorkspace] = useState<WorkspaceMetaData | undefined>(undefined);
const { resources, setResources, tree, operations } = useResources();
const { resources, tree, operations } = useResources();
const { socket } = useCommunication();
const { publishError } = useError();
const { wid } = useParams();
Expand All @@ -38,9 +31,9 @@ export function WorkspaceProvider({ children }: { children: React.ReactNode }) {

async function fetchWorkspace() {
const { id, name, resources } = await services.getWorkspace(wid!);
console.log('fetchWorkspace', resources);
setWorkspace({ id, name });
setResources(resources);
tree.setTree(resources);
operations.setResources(resources);
}
socket.emit('joinWorkspace', wid);
fetchWorkspace().catch(publishError);
Expand All @@ -50,8 +43,21 @@ export function WorkspaceProvider({ children }: { children: React.ReactNode }) {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wid, services, socket, publishError]);

const newOperations: WorkspaceContextType['operations'] = {
createResource: operations.createResource,
deleteResource: operations.deleteResource,
updateResource: operations.updateResource,
moveResource: operations.moveResource,
};

useEffect(() => {
console.log('resources: ', resources);
}, [resources]);

return (
<WorkspaceContext.Provider value={{ workspace, resources, operations, nodes: tree.nodes }}>
<WorkspaceContext.Provider
value={{ workspace, resources: Object.values(resources), operations: newOperations, nodes: tree.nodes }}
>
{children}
</WorkspaceContext.Provider>
);
Expand Down
40 changes: 28 additions & 12 deletions code/client/src/ui/contexts/workspace/useResources.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,36 @@
import {
ResourceType,
WorkspaceResource,
WorkspaceResourceMetadata,
} from '@notespace/shared/src/workspace/types/resource';
import { ResourceType, WorkspaceResource } from '@notespace/shared/src/workspace/types/resource';
import useResourceService from '@services/resource/useResourceService';
import useSocketListeners from '@services/communication/socket/useSocketListeners';
import { useCommunication } from '@ui/contexts/communication/useCommunication';
import { useState } from 'react';
import useWorkspaceTree from '@domain/workspaces/tree/useWorkspaceTree';
import { WorkspaceTreeNodes } from '@domain/workspaces/tree/types';

function useResources() {
export type UseResourcesType = {
resources: WorkspaceTreeNodes;
tree: ReturnType<typeof useWorkspaceTree>;
operations: {
setResources: (resources: WorkspaceTreeNodes) => void;
createResource: (name: string, type: ResourceType, parent?: string) => Promise<void>;
deleteResource: (id: string) => Promise<void>;
updateResource: (id: string, newProps: Partial<WorkspaceResource>) => Promise<void>;
moveResource: (id: string, parent: string) => Promise<void>;
};
};

function useResources(): UseResourcesType {
const service = useResourceService();
const { socket } = useCommunication();
const [resources, setResources] = useState<WorkspaceResource[]>([]);
const [resources, setResources] = useState<WorkspaceTreeNodes>({});
const tree = useWorkspaceTree();

function onSetResources(resources: WorkspaceTreeNodes) {
setResources(resources);
tree.setTree(resources);
}

function onCreateResource(resource: WorkspaceResource) {
setResources([...resources, resource]);
setResources({ ...resources, [resource.id]: resource });
tree.addNode(resource);
}

Expand All @@ -31,17 +45,19 @@ function useResources() {
return [id, ...resource.children.flatMap(childId => getChildren(childId))];
};
const idsToRemove = getChildren(id);
setResources(resources.filter(resource => !idsToRemove.includes(resource.id)));
const newResources = { ...resources };
idsToRemove.forEach(id => delete newResources[id]);
setResources(newResources);
tree.removeNode(id);
}

async function deleteResource(id: string) {
await service.deleteResource(id);
}

function onUpdateResource(resource: Partial<WorkspaceResourceMetadata>) {
function onUpdateResource(resource: Partial<WorkspaceResource>) {
if (!resource.id) throw new Error('Resource id is required');
setResources(resources.map(res => (res.id === resource.id ? { ...res, ...resource } : res)));
setResources({ ...resources, [resource.id]: { ...resources[resource.id], ...resource } });
if (resource.name) tree.updateNode(resource.id, resource.name);
if (resource.parent) tree.moveNode(resource.id, resource.parent);
}
Expand All @@ -63,9 +79,9 @@ function useResources() {

return {
resources,
setResources,
tree,
operations: {
setResources: onSetResources,
createResource,
deleteResource,
updateResource,
Expand Down
13 changes: 9 additions & 4 deletions code/server/src/sql/create_tables.sql
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
begin;

-- Create the pgcrypto extension if it doesn't already exist
create extension if not exists "pgcrypto";

-- Create the enum type for resource_type
create type resource_type as enum ('D', 'F');

-- Create the workspace table
create table if not exists workspace (
id char(12) primary key default encode(gen_random_bytes(8), 'base64'),
name text not null,
created_at timestamp not null default now(),
updated_at timestamp not null default now()
);

-- Create the resource table
create table if not exists resource (
id char(12) primary key default encode(gen_random_bytes(8), 'base64'),
workspace varchar not null references workspace(id) on delete cascade,
workspace char(12) not null references workspace(id) on delete cascade,
name text not null,
type resource_type not null,
created_at timestamp not null default now(),
updated_at timestamp not null default now(),
children char(12)[] not null default '{}',
parent char(12) references resource(id) on delete cascade
parent char(12) default null references resource(id) on delete cascade,
children char(12)[] not null default '{}'::char(12)[] -- Array of resource ids
);

commit;
commit;
Loading

0 comments on commit 8a86046

Please sign in to comment.