Skip to main content

Project Management

The Project Management module provides comprehensive functionality for creating, managing, and organizing projects within workspaces, including member management and project lifecycle operations.

Overview

The sdk.projects module handles:

  • Project CRUD operations
  • Project member management and roles
  • Project archiving and lifecycle management
  • Multi-workspace project queries
  • Project-specific permissions and access control

Installation & Setup

import { WorkspacesSDK } from '@burdenoff/workspaces-sdk';

const sdk = new WorkspacesSDK({
endpoint: 'https://api.your-domain.com/graphql',
accessToken: 'your_access_token',
refreshToken: 'your_refresh_token'
});

Core Methods

Create Project

Create a new project within a workspace.

const newProject = await sdk.projects.createProject({
name: 'My New Project',
workspaceID: 'workspace-id',
description: 'Project description',
settings: {
visibility: 'private',
allowContributions: true
}
});

console.log('Created project:', newProject);
// Output: { id: 'project-id', name: 'My New Project', ownerID: 'user-id', ... }

Get Project

Retrieve detailed project information including members.

const project = await sdk.projects.getProject('project-id');

console.log('Project details:', project);
console.log('Project members:', project?.members);

Edit Project

Update project information and settings.

const updatedProject = await sdk.projects.editProject({
id: 'project-id',
name: 'Updated Project Name',
description: 'Updated description',
settings: {
visibility: 'public'
}
});

console.log('Updated project:', updatedProject);

Archive/Unarchive Project

Manage project lifecycle through archiving.

// Archive a project
const archivedProject = await sdk.projects.archiveProject('project-id');
console.log('Archived at:', archivedProject.archivedAt);

// Unarchive a project
const unarchivedProject = await sdk.projects.unarchiveProject('project-id');
console.log('Project reactivated:', unarchivedProject.archivedAt === null);

Project Member Management

Get Project Members

Retrieve all members of a specific project.

const members = await sdk.projects.getProjectMembers('project-id');

console.log('Project members:', members);
// Output: [{ id: 'member-id', userID: 'user-id', role: 'admin', user: { name: 'John Doe', email: '...' } }]

Update Member Role

Change a project member's role.

const updatedMember = await sdk.projects.updateProjectMemberRole({
projectID: 'project-id',
userID: 'user-id',
role: 'maintainer'
});

console.log('Updated member role:', updatedMember.role);

Remove Project Member

Remove a member from a project.

const success = await sdk.projects.removeProjectMember({
id: 'member-id',
projectID: 'project-id',
userID: 'user-id'
});

console.log('Member removed:', success);

Multi-Workspace Operations

Get Workspace Projects

Retrieve projects from one or more workspaces.

const workspaceProjects = await sdk.projects.getWorkspaceProjects([
'workspace-id-1',
'workspace-id-2'
]);

workspaceProjects.forEach(wp => {
console.log(`Workspace ${wp.workspaceID} has ${wp.projects.length} projects`);
});

Advanced Usage

Project Management Class

class ProjectManager {
constructor(private sdk: WorkspacesSDK) {}

async createProjectWithTeam(
projectData: CreateProjectInput,
teamMembers: { userID: string; role: string }[]
) {
// Create the project
const project = await this.sdk.projects.createProject(projectData);

// Add team members (this would require additional API methods)
const memberResults = await Promise.allSettled(
teamMembers.map(member =>
// Note: This would require an addProjectMember method
this.addProjectMember(project.id, member)
)
);

return {
project,
memberResults: memberResults.map((result, index) => ({
userID: teamMembers[index].userID,
success: result.status === 'fulfilled',
error: result.status === 'rejected' ? result.reason : null
}))
};
}

async duplicateProject(sourceProjectId: string, newName: string) {
const sourceProject = await this.sdk.projects.getProject(sourceProjectId);
if (!sourceProject) {
throw new Error('Source project not found');
}

// Create new project with similar settings
const newProject = await this.sdk.projects.createProject({
name: newName,
workspaceID: sourceProject.workspaceID,
description: `Copy of ${sourceProject.name}`,
// Copy other relevant settings
});

return newProject;
}

async getProjectsWithStatus(workspaceIds: string[]) {
const workspaceProjects = await this.sdk.projects.getWorkspaceProjects(workspaceIds);

return workspaceProjects.map(wp => ({
workspaceID: wp.workspaceID,
active: wp.projects.filter(p => !p.archivedAt).length,
archived: wp.projects.filter(p => p.archivedAt).length,
total: wp.projects.length,
projects: wp.projects
}));
}
}

Project Analytics

class ProjectAnalytics {
constructor(private sdk: WorkspacesSDK) {}

async getProjectInsights(projectId: string) {
const project = await this.sdk.projects.getProject(projectId);
if (!project) return null;

const members = await this.sdk.projects.getProjectMembers(projectId);

return {
project: {
id: project.id,
name: project.name,
createdAt: project.createdAt,
isArchived: !!project.archivedAt
},
members: {
total: members.length,
byRole: this.groupMembersByRole(members),
recent: members
.sort((a, b) => new Date(b.addedAt || '').getTime() - new Date(a.addedAt || '').getTime())
.slice(0, 5)
},
timeline: {
daysSinceCreation: Math.floor(
(Date.now() - new Date(project.createdAt).getTime()) / (1000 * 60 * 60 * 24)
)
}
};
}

private groupMembersByRole(members: ProjectMember[]) {
return members.reduce((acc, member) => {
acc[member.role] = (acc[member.role] || 0) + 1;
return acc;
}, {} as Record<string, number>);
}

async getWorkspaceProjectSummary(workspaceId: string) {
const workspaceProjects = await this.sdk.projects.getWorkspaceProjects([workspaceId]);
const projects = workspaceProjects[0]?.projects || [];

return {
total: projects.length,
active: projects.filter(p => !p.archivedAt).length,
archived: projects.filter(p => p.archivedAt).length,
recentlyCreated: projects
.filter(p => {
const createdAt = new Date(p.createdAt);
const daysAgo = (Date.now() - createdAt.getTime()) / (1000 * 60 * 60 * 24);
return daysAgo <= 30;
}).length,
avgMembersPerProject: projects.length > 0
? (await Promise.all(
projects.map(p => this.sdk.projects.getProjectMembers(p.id))
)).reduce((sum, members) => sum + members.length, 0) / projects.length
: 0
};
}
}

Project Permission Manager

class ProjectPermissionManager {
constructor(private sdk: WorkspacesSDK) {}

async checkProjectAccess(projectId: string, userId: string) {
try {
const members = await this.sdk.projects.getProjectMembers(projectId);
const userMember = members.find(m => m.userID === userId);

return {
hasAccess: !!userMember,
role: userMember?.role,
permissions: this.getRolePermissions(userMember?.role)
};
} catch (error) {
return {
hasAccess: false,
role: null,
permissions: []
};
}
}

async ensureProjectAccess(projectId: string, userId: string, requiredRole: string = 'viewer') {
const access = await this.checkProjectAccess(projectId, userId);

if (!access.hasAccess) {
throw new Error('User does not have access to this project');
}

const roleHierarchy = ['viewer', 'contributor', 'maintainer', 'admin', 'owner'];
const userRoleIndex = roleHierarchy.indexOf(access.role || '');
const requiredRoleIndex = roleHierarchy.indexOf(requiredRole);

if (userRoleIndex < requiredRoleIndex) {
throw new Error(`Insufficient permissions. Required: ${requiredRole}, Current: ${access.role}`);
}

return access;
}

private getRolePermissions(role?: string): string[] {
const permissions: Record<string, string[]> = {
viewer: ['read'],
contributor: ['read', 'write'],
maintainer: ['read', 'write', 'manage_members'],
admin: ['read', 'write', 'manage_members', 'manage_project'],
owner: ['read', 'write', 'manage_members', 'manage_project', 'delete_project']
};

return permissions[role || ''] || [];
}
}

Error Handling

try {
const project = await sdk.projects.getProject('project-id');
} catch (error) {
if (error.message.includes('Project not found')) {
console.log('Project does not exist');
} else if (error.message.includes('Permission denied')) {
console.log('Not authorized to access this project');
} else if (error.message.includes('Project archived')) {
console.log('Project is archived');
} else {
console.error('Unexpected error:', error);
}
}

TypeScript Types

interface Project {
id: string;
name: string;
description?: string;
workspaceID: string;
ownerID: string;
createdAt: string;
updatedAt?: string;
archivedAt?: string;
members?: ProjectMember[];
settings?: ProjectSettings;
}

interface ProjectMember {
id: string;
projectID: string;
userID: string;
role: string;
addedAt?: string;
updatedAt?: string;
user: {
id: string;
email: string;
name: string;
};
}

interface CreateProjectInput {
name: string;
workspaceID: string;
description?: string;
settings?: ProjectSettings;
}

interface EditProjectInput {
id: string;
name?: string;
description?: string;
settings?: ProjectSettings;
}

interface ProjectSettings {
visibility?: 'public' | 'private';
allowContributions?: boolean;
requireApproval?: boolean;
}

interface UpdateProjectMemberRoleInput {
projectID: string;
userID: string;
role: string;
}

interface RemoveProjectMemberInput {
id: string;
projectID: string;
userID: string;
}

Best Practices

1. Project Validation

function validateProject(project: CreateProjectInput): string[] {
const errors: string[] = [];

if (!project.name?.trim()) {
errors.push('Project name is required');
} else if (project.name.length > 100) {
errors.push('Project name must be less than 100 characters');
}

if (project.description && project.description.length > 1000) {
errors.push('Project description must be less than 1000 characters');
}

return errors;
}

2. Safe Project Operations

class SafeProjectOperations {
constructor(private sdk: WorkspacesSDK) {}

async safeArchiveProject(projectId: string, userId: string) {
// Check if user has permission
const project = await this.sdk.projects.getProject(projectId);
if (!project) {
throw new Error('Project not found');
}

if (project.ownerID !== userId) {
// Check if user is admin
const members = await this.sdk.projects.getProjectMembers(projectId);
const userMember = members.find(m => m.userID === userId);

if (!userMember || !['admin', 'owner'].includes(userMember.role)) {
throw new Error('Insufficient permissions to archive project');
}
}

return await this.sdk.projects.archiveProject(projectId);
}

async safeUpdateMemberRole(
projectId: string,
targetUserId: string,
newRole: string,
requestingUserId: string
) {
const members = await this.sdk.projects.getProjectMembers(projectId);
const requestingMember = members.find(m => m.userID === requestingUserId);
const targetMember = members.find(m => m.userID === targetUserId);

if (!requestingMember || !targetMember) {
throw new Error('User not found in project');
}

// Only admins and owners can change roles
if (!['admin', 'owner'].includes(requestingMember.role)) {
throw new Error('Insufficient permissions to change member roles');
}

// Can't change owner role or demote another admin (unless you're owner)
if (targetMember.role === 'owner' ||
(targetMember.role === 'admin' && requestingMember.role !== 'owner')) {
throw new Error('Cannot modify this user\'s role');
}

return await this.sdk.projects.updateProjectMemberRole({
projectID: projectId,
userID: targetUserId,
role: newRole
});
}
}

3. Project Templates

class ProjectTemplateManager {
constructor(private sdk: WorkspacesSDK) {}

private templates: Record<string, Partial<CreateProjectInput>> = {
'basic': {
settings: {
visibility: 'private',
allowContributions: false,
requireApproval: true
}
},
'collaborative': {
settings: {
visibility: 'public',
allowContributions: true,
requireApproval: false
}
},
'enterprise': {
settings: {
visibility: 'private',
allowContributions: true,
requireApproval: true
}
}
};

async createFromTemplate(
templateName: string,
projectData: Pick<CreateProjectInput, 'name' | 'workspaceID' | 'description'>
) {
const template = this.templates[templateName];
if (!template) {
throw new Error(`Template '${templateName}' not found`);
}

return await this.sdk.projects.createProject({
...projectData,
...template
});
}
}

Integration Examples

React Hook for Project Management

import { useState, useEffect } from 'react';
import { WorkspacesSDK, Project, ProjectMember } from '@burdenoff/workspaces-sdk';

export function useProject(sdk: WorkspacesSDK, projectId: string) {
const [project, setProject] = useState<Project | null>(null);
const [members, setMembers] = useState<ProjectMember[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
async function fetchProject() {
try {
setLoading(true);
const [projectData, membersData] = await Promise.all([
sdk.projects.getProject(projectId),
sdk.projects.getProjectMembers(projectId)
]);

setProject(projectData);
setMembers(membersData);
setError(null);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
setProject(null);
setMembers([]);
} finally {
setLoading(false);
}
}

if (projectId) {
fetchProject();
}
}, [sdk, projectId]);

const updateProject = async (updates: Partial<Project>) => {
if (!project) return;

try {
const updatedProject = await sdk.projects.editProject({
id: project.id,
...updates
});
setProject(updatedProject);
return updatedProject;
} catch (err) {
setError(err instanceof Error ? err.message : 'Update failed');
throw err;
}
};

const archiveProject = async () => {
if (!project) return;

try {
const archivedProject = await sdk.projects.archiveProject(project.id);
setProject(archivedProject);
return archivedProject;
} catch (err) {
setError(err instanceof Error ? err.message : 'Archive failed');
throw err;
}
};

return {
project,
members,
loading,
error,
updateProject,
archiveProject
};
}

This documentation provides comprehensive coverage of the Project Management module, including practical examples, security considerations, and best practices for real-world usage.