Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/agent-planning-scenarios.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ User right-clicks any node and sees:

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ πŸ“‹ Edit Node β”‚
β”‚ πŸ“‹ Edit Work Item β”‚
β”‚ πŸ”— Add Relationship β”‚
β”‚ 🎯 Set Priority β”‚
β”‚ ───────────────────────── β”‚
Expand Down
4 changes: 2 additions & 2 deletions docs/features/graph-creation.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,12 @@ GraphDone uses a **graph-first approach** where work items exist as connected no

**Quick Node Creation:**
1. **Right-click** on empty graph space
2. **Select** "Add Node" from context menu
2. **Select** "Add Work Item" from context menu
3. **Choose** node type from comprehensive list
4. **Fill** essential information and save

**Detailed Node Creation:**
1. **Click** primary "Add Node" button
1. **Click** primary "Add Work Item" button
2. **Complete** the node creation form:

**Essential Fields:**
Expand Down
3 changes: 2 additions & 1 deletion packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"typecheck": "tsc --noEmit",
"clean": "rm -rf dist coverage",
"db:seed": "tsx src/scripts/seed.ts",
"create-admin": "tsx src/scripts/create-admin.ts"
"create-admin": "tsx src/scripts/create-admin.ts",
"create-welcome-graphs": "tsx src/scripts/create-welcome-graphs.ts"
},
"dependencies": {
"@apollo/server": "^4.9.0",
Expand Down
16 changes: 13 additions & 3 deletions packages/server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { typeDefs } from './schema/neo4j-schema.js';
import { authTypeDefs } from './schema/auth-schema.js';
import { authOnlyTypeDefs } from './schema/auth-only-schema.js';
import { sqliteAuthResolvers } from './resolvers/sqlite-auth.js';
import { graphProtectionResolvers } from './resolvers/graph-protection.js';
import { extractUserFromToken } from './middleware/auth.js';
import { mergeTypeDefs } from '@graphql-tools/merge';
import { driver, NEO4J_URI } from './db.js';
Expand Down Expand Up @@ -293,16 +294,25 @@ async function startServer() {
console.log('ℹ️ Default admin setup completed'); // eslint-disable-line no-console
}

// Welcome graphs are now created per-user on first login (handled by frontend)

// Merge type definitions (Neo4j schema + auth schema)
const mergedTypeDefs = mergeTypeDefs([typeDefs, authTypeDefs]);

// Create Neo4jGraphQL instance for graph data with SQLite auth resolvers override
// Create Neo4jGraphQL instance with custom resolvers
const neoSchema = new Neo4jGraphQL({
typeDefs: mergedTypeDefs,
driver,
resolvers: {
// Override auth resolvers to use SQLite instead of Neo4j User nodes
...sqliteAuthResolvers,
Mutation: {
// Auth resolvers (SQLite-based)
...sqliteAuthResolvers.Mutation,
// Graph protection (prevent Welcome graph deletion)
...graphProtectionResolvers.Mutation,
},
Query: {
...sqliteAuthResolvers.Query,
}
},
});

Expand Down
73 changes: 73 additions & 0 deletions packages/server/src/resolvers/graph-protection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { GraphQLError } from 'graphql';
import { Driver } from 'neo4j-driver';

const PROTECTED_GRAPHS = ['welcome-graph-shared'];

async function isProtectedGraph(driver: Driver, graphId: string): Promise<boolean> {
if (PROTECTED_GRAPHS.includes(graphId)) {
return true;
}

const session = driver.session();
try {
const result = await session.run(
`MATCH (g:Graph {id: $graphId}) RETURN g.id IN $protectedIds AS isProtected`,
{ graphId, protectedIds: PROTECTED_GRAPHS }
);
return result.records[0]?.get('isProtected') || false;
} finally {
await session.close();
}
}


export const graphProtectionResolvers = {
Mutation: {
// Protect graphs from deletion
deleteGraphs: async (
_parent: any,
args: { where?: { id?: string; id_IN?: string[] } },
context: { driver: Driver | null }
) => {
if (!context.driver) {
throw new GraphQLError('Database connection unavailable');
}

const graphId = args.where?.id;
const graphIds = args.where?.id_IN;
const idsToCheck = graphId ? [graphId] : (graphIds || []);

for (const id of idsToCheck) {
if (await isProtectedGraph(context.driver, id)) {
throw new GraphQLError('The Welcome graph is read-only and cannot be deleted. This is a system tutorial graph for all users.', {
extensions: {
code: 'FORBIDDEN',
protectedGraph: true,
graphId: id
}
});
}
}

const session = context.driver.session();
try {
const result = await session.run(
`
MATCH (g:Graph)
WHERE g.id = $id ${graphIds ? 'OR g.id IN $ids' : ''}
DETACH DELETE g
RETURN count(g) AS nodesDeleted
`,
{ id: graphId, ids: graphIds }
);

return {
nodesDeleted: result.records[0]?.get('nodesDeleted')?.toNumber() || 0
};
} finally {
await session.close();
}
}

}
};
2 changes: 1 addition & 1 deletion packages/server/src/resolvers/sqlite-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ export const sqliteAuthResolvers = {

const token = generateToken(user.id, user.email, user.role);
console.log(`βœ… Login successful: ${user.username} (${user.role})`);

return {
token,
user: {
Expand Down
38 changes: 38 additions & 0 deletions packages/server/src/scripts/create-welcome-graphs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { driver } from '../db.js';
import { createSharedWelcomeGraph, sharedWelcomeGraphExists } from '../services/onboarding.js';

async function ensureSharedWelcomeGraph() {
console.log('πŸŽ‰ Ensuring shared Welcome graph exists...\n');

try {
const exists = await sharedWelcomeGraphExists(driver);

if (exists) {
console.log('⏭️ Shared Welcome graph already exists - skipping creation\n');
console.log('========================================');
console.log(' Welcome Graph Already Exists ');
console.log('========================================\n');
} else {
await createSharedWelcomeGraph(driver);
console.log('βœ… Shared Welcome graph created successfully!\n');
console.log('========================================');
console.log(' Welcome Graph Created ');
console.log('========================================');
console.log('β€’ Graph Name: Welcome');
console.log('β€’ Type: PROJECT');
console.log('β€’ Shared: Yes (all users can access)');
console.log('β€’ Permissions: Read-only for all users');
console.log('β€’ Contains: 6 tutorial nodes');
console.log('========================================\n');
}

} catch (error: any) {
console.error('❌ Failed to create shared Welcome graph:', error);
process.exit(1);
} finally {
await driver.close();
process.exit(0);
}
}

ensureSharedWelcomeGraph();
160 changes: 107 additions & 53 deletions packages/server/src/scripts/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,64 +28,107 @@ async function seed() {
await session.run('MATCH (n) DETACH DELETE n');
// eslint-disable-next-line no-console
console.log('✨ Cleared existing data');

// Create work items with proper team IDs

// Create graphs first
const graphs = [
{
id: 'welcome-graph-shared',
name: 'Welcome to GraphDone',
description: 'A tutorial graph to help you understand GraphDone',
isPublic: true,
teamId: 'team-1',
userId: 'user-1'
},
{
id: 'graph-project-alpha',
name: 'Project Alpha',
description: 'Main development project',
isPublic: false,
teamId: 'team-1',
userId: 'user-1'
},
{
id: 'graph-test-beta',
name: 'Test Graph Beta',
description: 'Testing and experimentation',
isPublic: false,
teamId: 'team-1',
userId: 'user-2'
}
];

for (const graph of graphs) {
await session.run(
`CREATE (g:Graph {
id: $id,
name: $name,
description: $description,
isPublic: $isPublic,
teamId: $teamId,
userId: $userId,
createdAt: datetime(),
updatedAt: datetime()
})`,
graph
);
}
// eslint-disable-next-line no-console
console.log(`βœ… Created ${graphs.length} graphs`);

// Create work items with proper team IDs and graph assignments
const workItems = [
// Infrastructure & Setup
{ id: 'wi-1', title: 'Set up Neo4j database', type: 'TASK', status: 'COMPLETED', teamId: 'team-1', userId: 'user-1' },
{ id: 'wi-2', title: 'Configure GraphQL schema', type: 'TASK', status: 'COMPLETED', teamId: 'team-1', userId: 'user-1' },
{ id: 'wi-3', title: 'Implement authentication', type: 'TASK', status: 'IN_PROGRESS', teamId: 'team-1', userId: 'user-2' },
{ id: 'wi-4', title: 'Set up CI/CD pipeline', type: 'TASK', status: 'PLANNED', teamId: 'team-1', userId: 'user-3' },

// Core Features
{ id: 'wi-5', title: 'Graph visualization system', type: 'MILESTONE', status: 'IN_PROGRESS', teamId: 'team-1', userId: 'user-1' },
{ id: 'wi-6', title: 'Implement D3.js force layout', type: 'TASK', status: 'COMPLETED', teamId: 'team-1', userId: 'user-2' },
{ id: 'wi-7', title: 'Add node drag interaction', type: 'TASK', status: 'COMPLETED', teamId: 'team-1', userId: 'user-2' },
{ id: 'wi-8', title: 'Create edge rendering system', type: 'TASK', status: 'IN_PROGRESS', teamId: 'team-1', userId: 'user-3' },

// Ideas & Proposals
{ id: 'wi-9', title: 'AI agent integration', type: 'IDEA', status: 'PROPOSED', teamId: 'team-1', userId: 'user-4' },
{ id: 'wi-10', title: 'Mobile app development', type: 'IDEA', status: 'PROPOSED', teamId: 'team-1', userId: 'user-5' },
{ id: 'wi-11', title: 'Real-time collaboration', type: 'IDEA', status: 'PROPOSED', teamId: 'team-1', userId: 'user-1' },

// Outcomes
{ id: 'wi-12', title: 'Production-ready graph system', type: 'OUTCOME', status: 'PLANNED', teamId: 'team-1', userId: 'user-1' },
{ id: 'wi-13', title: 'Scalable architecture', type: 'OUTCOME', status: 'IN_PROGRESS', teamId: 'team-1', userId: 'user-2' },

// Additional tasks for testing
{ id: 'wi-14', title: 'Write unit tests', type: 'TASK', status: 'IN_PROGRESS', teamId: 'team-1', userId: 'user-3' },
{ id: 'wi-15', title: 'Performance optimization', type: 'TASK', status: 'PLANNED', teamId: 'team-1', userId: 'user-4' },
{ id: 'wi-16', title: 'Documentation update', type: 'TASK', status: 'PLANNED', teamId: 'team-1', userId: 'user-5' },

// More features
{ id: 'wi-17', title: 'User dashboard', type: 'MILESTONE', status: 'PLANNED', teamId: 'team-1', userId: 'user-1' },
{ id: 'wi-18', title: 'Analytics module', type: 'MILESTONE', status: 'PROPOSED', teamId: 'team-1', userId: 'user-2' },
{ id: 'wi-19', title: 'Export functionality', type: 'TASK', status: 'PROPOSED', teamId: 'team-1', userId: 'user-3' },
{ id: 'wi-20', title: 'Import from other tools', type: 'TASK', status: 'PROPOSED', teamId: 'team-1', userId: 'user-4' },

// Test data variations
{ id: 'wi-21', title: 'Test WithAPOC', type: 'TASK', status: 'COMPLETED', teamId: 'team-1', userId: 'user-1' },
{ id: 'wi-22', title: 'testUI Test', type: 'TASK', status: 'IN_PROGRESS', teamId: 'team-1', userId: 'user-2' },
{ id: 'wi-23', title: 'Form validation testing', type: 'TASK', status: 'PLANNED', teamId: 'team-1', userId: 'user-3' },
{ id: 'wi-24', title: 'Edge case handling', type: 'TASK', status: 'PROPOSED', teamId: 'team-1', userId: 'user-4' },
// Welcome Graph Items (tutorial)
{ id: 'wi-welcome-1', title: 'Welcome to GraphDone!', type: 'MILESTONE', status: 'COMPLETED', teamId: 'team-1', userId: 'user-1', graphId: 'welcome-graph-shared' },
{ id: 'wi-welcome-2', title: 'Create your first work item', type: 'TASK', status: 'COMPLETED', teamId: 'team-1', userId: 'user-1', graphId: 'welcome-graph-shared' },
{ id: 'wi-welcome-3', title: 'Connect work items together', type: 'TASK', status: 'IN_PROGRESS', teamId: 'team-1', userId: 'user-1', graphId: 'welcome-graph-shared' },
{ id: 'wi-welcome-4', title: 'Explore different views', type: 'TASK', status: 'PLANNED', teamId: 'team-1', userId: 'user-1', graphId: 'welcome-graph-shared' },

// Project Alpha - Infrastructure & Setup
{ id: 'wi-1', title: 'Set up Neo4j database', type: 'TASK', status: 'COMPLETED', teamId: 'team-1', userId: 'user-1', graphId: 'graph-project-alpha' },
{ id: 'wi-2', title: 'Configure GraphQL schema', type: 'TASK', status: 'COMPLETED', teamId: 'team-1', userId: 'user-1', graphId: 'graph-project-alpha' },
{ id: 'wi-3', title: 'Implement authentication', type: 'TASK', status: 'IN_PROGRESS', teamId: 'team-1', userId: 'user-2', graphId: 'graph-project-alpha' },
{ id: 'wi-4', title: 'Set up CI/CD pipeline', type: 'TASK', status: 'PLANNED', teamId: 'team-1', userId: 'user-3', graphId: 'graph-project-alpha' },

// Strategic items
{ id: 'wi-25', title: 'Q1 2024 Planning', type: 'MILESTONE', status: 'COMPLETED', teamId: 'team-1', userId: 'user-1' },
{ id: 'wi-26', title: 'Product roadmap review', type: 'OUTCOME', status: 'IN_PROGRESS', teamId: 'team-1', userId: 'user-2' },
{ id: 'wi-27', title: 'Customer feedback analysis', type: 'TASK', status: 'PLANNED', teamId: 'team-1', userId: 'user-3' },
{ id: 'wi-28', title: 'Market research', type: 'TASK', status: 'PROPOSED', teamId: 'team-1', userId: 'user-4' },
// Project Alpha - Core Features
{ id: 'wi-5', title: 'Graph visualization system', type: 'MILESTONE', status: 'IN_PROGRESS', teamId: 'team-1', userId: 'user-1', graphId: 'graph-project-alpha' },
{ id: 'wi-6', title: 'Implement D3.js force layout', type: 'TASK', status: 'COMPLETED', teamId: 'team-1', userId: 'user-2', graphId: 'graph-project-alpha' },
{ id: 'wi-7', title: 'Add node drag interaction', type: 'TASK', status: 'COMPLETED', teamId: 'team-1', userId: 'user-2', graphId: 'graph-project-alpha' },
{ id: 'wi-8', title: 'Create edge rendering system', type: 'TASK', status: 'IN_PROGRESS', teamId: 'team-1', userId: 'user-3', graphId: 'graph-project-alpha' },

// Technical debt
{ id: 'wi-29', title: 'Refactor graph engine', type: 'TASK', status: 'PLANNED', teamId: 'team-1', userId: 'user-5' },
{ id: 'wi-30', title: 'Update dependencies', type: 'TASK', status: 'IN_PROGRESS', teamId: 'team-1', userId: 'user-1' },
{ id: 'wi-31', title: 'Security audit', type: 'MILESTONE', status: 'PLANNED', teamId: 'team-1', userId: 'user-2' },
{ id: 'wi-32', title: 'Performance benchmarking', type: 'TASK', status: 'PROPOSED', teamId: 'team-1', userId: 'user-3' }
// Project Alpha - Ideas & More
{ id: 'wi-9', title: 'AI agent integration', type: 'IDEA', status: 'PROPOSED', teamId: 'team-1', userId: 'user-4', graphId: 'graph-project-alpha' },
{ id: 'wi-10', title: 'Mobile app development', type: 'IDEA', status: 'PROPOSED', teamId: 'team-1', userId: 'user-5', graphId: 'graph-project-alpha' },
{ id: 'wi-11', title: 'Real-time collaboration', type: 'IDEA', status: 'PROPOSED', teamId: 'team-1', userId: 'user-1', graphId: 'graph-project-alpha' },
{ id: 'wi-12', title: 'Production-ready graph system', type: 'OUTCOME', status: 'PLANNED', teamId: 'team-1', userId: 'user-1', graphId: 'graph-project-alpha' },
{ id: 'wi-13', title: 'Scalable architecture', type: 'OUTCOME', status: 'IN_PROGRESS', teamId: 'team-1', userId: 'user-2', graphId: 'graph-project-alpha' },

// Test Graph Beta - Testing items
{ id: 'wi-14', title: 'Write unit tests', type: 'TASK', status: 'IN_PROGRESS', teamId: 'team-1', userId: 'user-3', graphId: 'graph-test-beta' },
{ id: 'wi-15', title: 'Performance optimization', type: 'TASK', status: 'PLANNED', teamId: 'team-1', userId: 'user-4', graphId: 'graph-test-beta' },
{ id: 'wi-16', title: 'Documentation update', type: 'TASK', status: 'PLANNED', teamId: 'team-1', userId: 'user-5', graphId: 'graph-test-beta' },
{ id: 'wi-17', title: 'User dashboard', type: 'MILESTONE', status: 'PLANNED', teamId: 'team-1', userId: 'user-1', graphId: 'graph-test-beta' },
{ id: 'wi-18', title: 'Analytics module', type: 'MILESTONE', status: 'PROPOSED', teamId: 'team-1', userId: 'user-2', graphId: 'graph-test-beta' },
{ id: 'wi-19', title: 'Export functionality', type: 'TASK', status: 'PROPOSED', teamId: 'team-1', userId: 'user-3', graphId: 'graph-test-beta' },
{ id: 'wi-20', title: 'Import from other tools', type: 'TASK', status: 'PROPOSED', teamId: 'team-1', userId: 'user-4', graphId: 'graph-test-beta' },
{ id: 'wi-21', title: 'Test WithAPOC', type: 'TASK', status: 'COMPLETED', teamId: 'team-1', userId: 'user-1', graphId: 'graph-test-beta' },
{ id: 'wi-22', title: 'testUI Test', type: 'TASK', status: 'IN_PROGRESS', teamId: 'team-1', userId: 'user-2', graphId: 'graph-test-beta' },
{ id: 'wi-23', title: 'Form validation testing', type: 'TASK', status: 'PLANNED', teamId: 'team-1', userId: 'user-3', graphId: 'graph-test-beta' },
{ id: 'wi-24', title: 'Edge case handling', type: 'TASK', status: 'PROPOSED', teamId: 'team-1', userId: 'user-4', graphId: 'graph-test-beta' },
{ id: 'wi-25', title: 'Q1 2024 Planning', type: 'MILESTONE', status: 'COMPLETED', teamId: 'team-1', userId: 'user-1', graphId: 'graph-test-beta' },
{ id: 'wi-26', title: 'Product roadmap review', type: 'OUTCOME', status: 'IN_PROGRESS', teamId: 'team-1', userId: 'user-2', graphId: 'graph-test-beta' },
{ id: 'wi-27', title: 'Customer feedback analysis', type: 'TASK', status: 'PLANNED', teamId: 'team-1', userId: 'user-3', graphId: 'graph-test-beta' },
{ id: 'wi-28', title: 'Market research', type: 'TASK', status: 'PROPOSED', teamId: 'team-1', userId: 'user-4', graphId: 'graph-test-beta' },
{ id: 'wi-29', title: 'Refactor graph engine', type: 'TASK', status: 'PLANNED', teamId: 'team-1', userId: 'user-5', graphId: 'graph-test-beta' },
{ id: 'wi-30', title: 'Update dependencies', type: 'TASK', status: 'IN_PROGRESS', teamId: 'team-1', userId: 'user-1', graphId: 'graph-test-beta' },
{ id: 'wi-31', title: 'Security audit', type: 'MILESTONE', status: 'PLANNED', teamId: 'team-1', userId: 'user-2', graphId: 'graph-test-beta' },
{ id: 'wi-32', title: 'Performance benchmarking', type: 'TASK', status: 'PROPOSED', teamId: 'team-1', userId: 'user-3', graphId: 'graph-test-beta' }
];

// Create work items
// Create work items and link to graphs
for (const item of workItems) {
await session.run(
`CREATE (w:WorkItem {
`MATCH (g:Graph {id: $graphId})
CREATE (w:WorkItem {
id: $id,
title: $title,
type: $type,
Expand All @@ -106,7 +149,8 @@ async function seed() {
tags: $tags,
createdAt: datetime(),
updatedAt: datetime()
})`,
})
CREATE (w)-[:BELONGS_TO]->(g)`,
{
...item,
description: `Description for ${item.title}`,
Expand All @@ -125,12 +169,22 @@ async function seed() {

// Create edges (relationships between work items)
const edges = [
// Welcome graph edges
{ source: 'wi-welcome-1', target: 'wi-welcome-2', type: 'DEPENDS_ON' },
{ source: 'wi-welcome-2', target: 'wi-welcome-3', type: 'DEPENDS_ON' },
{ source: 'wi-welcome-3', target: 'wi-welcome-4', type: 'DEPENDS_ON' },

// Project Alpha edges
{ source: 'wi-1', target: 'wi-2', type: 'DEPENDS_ON' },
{ source: 'wi-2', target: 'wi-3', type: 'DEPENDS_ON' },
{ source: 'wi-5', target: 'wi-6', type: 'IS_PART_OF' },
{ source: 'wi-5', target: 'wi-7', type: 'IS_PART_OF' },
{ source: 'wi-5', target: 'wi-8', type: 'IS_PART_OF' },
{ source: 'wi-12', target: 'wi-5', type: 'DEPENDS_ON' }
{ source: 'wi-12', target: 'wi-5', type: 'DEPENDS_ON' },

// Test graph edges
{ source: 'wi-14', target: 'wi-15', type: 'DEPENDS_ON' },
{ source: 'wi-17', target: 'wi-18', type: 'IS_PART_OF' }
];

for (const edge of edges) {
Expand Down
Loading
Loading