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.