commit 52e8190211b465cc57df94624c6cfe7cc7a82b4b Author: Michael Chihlas Date: Thu Jan 22 14:38:53 2026 -0500 Initial commit: Backend API Phase 1a complete - FastAPI backend with JWT auth - PostgreSQL database schema - Trees and Sessions CRUD APIs - Export functionality (Markdown, Text, HTML) - Docker setup for local development - Alembic migrations diff --git a/.clauignore b/.clauignore new file mode 100644 index 00000000..945469e2 --- /dev/null +++ b/.clauignore @@ -0,0 +1,185 @@ +# .clauignore +# Files and folders that Claude Code should ignore +# Syntax is the same as .gitignore + +# ============================================ +# PERSONAL NOTES & SCRATCH FILES +# ============================================ +# Your personal notes that Claude doesn't need to see +MICHAEL-NOTES.md +MICHAEL-TODO.md +notes.md +scratch.md +TODO-personal.md + +# Personal folders +notes/ +scratch/ +personal/ +.notes/ + +# Any file with these prefixes +IGNORE-* +PERSONAL-* +SCRATCH-* +TEMP-* + +# ============================================ +# ENVIRONMENT & SECRETS +# ============================================ +# Never let Claude see secrets or local config +.env +.env.local +.env.*.local +secrets.json +credentials.json +*.pem +*.key +*.crt +id_rsa* + +# ============================================ +# DEPENDENCIES & BUILD ARTIFACTS +# ============================================ +# No need for Claude to read dependency folders + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +venv/ +ENV/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +.pytest_cache/ +.coverage +htmlcov/ + +# Node/JavaScript +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.npm +.yarn/ +package-lock.json +yarn.lock + +# ============================================ +# IDE & EDITOR FILES +# ============================================ +# IDE-specific files that clutter Claude's context +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store +Thumbs.db + +# ============================================ +# DATABASE & LOGS +# ============================================ +# Local database files and logs +*.sqlite +*.sqlite3 +*.db +*.log +logs/ +*.log.* + +# ============================================ +# TEMPORARY & GENERATED FILES +# ============================================ +# Files that are generated and don't need review +*.tmp +*.temp +.cache/ +.pytest_cache/ +.mypy_cache/ +.tox/ +coverage/ + +# Excel temporary files +~$*.xlsx +~$*.xls + +# ============================================ +# VERSION CONTROL +# ============================================ +# Let Claude ignore git metadata +.git/ +.gitignore +.gitattributes + +# ============================================ +# DOCUMENTATION EXCLUSIONS (if needed) +# ============================================ +# Uncomment these if you want Claude to skip certain docs +# CHANGELOG.md +# CONTRIBUTING.md +# LICENSE + +# ============================================ +# TESTING & CI/CD +# ============================================ +# Test coverage and CI files usually not needed +.coverage +.circleci/ +.github/ +.gitlab-ci.yml +.travis.yml + +# ============================================ +# GENERATED DOCUMENTATION +# ============================================ +# API docs that are auto-generated +docs/api/generated/ +docs/_build/ + +# ============================================ +# CUSTOM EXCLUSIONS FOR THIS PROJECT +# ============================================ + +# Old versions or backups +*.backup +*.bak +*.old +backup/ +old/ + +# Screenshots and media (unless needed) +# Uncomment if you have lots of images that clutter context +# screenshots/ +# *.png +# *.jpg +# *.gif +# *.mp4 + +# Large data files +# data/ +# *.csv +# *.json (if you have large JSON files) + +# ============================================ +# NOTES FOR FUTURE +# ============================================ +# Remember to add new personal note patterns here +# Example: If you create BRAINSTORM-*.md files, add: +# BRAINSTORM-* diff --git a/01-PROJECT-OVERVIEW.md b/01-PROJECT-OVERVIEW.md new file mode 100644 index 00000000..7002782f --- /dev/null +++ b/01-PROJECT-OVERVIEW.md @@ -0,0 +1,94 @@ +# Troubleshooting Decision Tree Application +## Project Overview + +### Vision +A decision tree troubleshooting application designed for MSP engineers to transform diagnostic processes into clean, professional documentation automatically. + +### Core Problem +MSP engineers like Michael face constant context switching between diverse technical issues (file shares, server outages, VPN failures, Active Directory problems). Each context switch: +- Takes 15-25 minutes to regain full focus +- Creates cognitive overhead and attention residue +- Contributes to burnout (research-backed) +- Makes consistent documentation challenging +- Results in lost tribal knowledge + +### Solution +An intelligent decision tree system that: +- Guides engineers through proven troubleshooting paths +- Captures decisions and notes automatically +- Generates professional ticket documentation +- Builds institutional knowledge +- Reduces cognitive load during high-stress situations + +### Success Criteria +**3-Month Goal:** Michael uses this tool for 50% of his tickets + +**Key Metrics:** +- Time saved per ticket +- Documentation quality improvement +- Reduction in "what did I do?" moments +- Team adoption rate +- Reduction in repeated troubleshooting attempts + +### Target Users +**Primary:** Senior Systems Engineers at MSPs managing Windows Server, Active Directory, Citrix, networking equipment + +**Secondary:** +- Junior engineers needing guided troubleshooting +- Onsite technicians following remote engineer instructions +- MSP teams wanting standardized procedures + +### Key Differentiators +1. **Automatic documentation generation** - No separate note-taking step +2. **On-the-fly customization** - Add custom branches when encountering edge cases +3. **Learning system** - Tracks common paths, suggests optimizations +4. **Automation integration** - Execute PowerShell/scripts directly from decision nodes +5. **Knowledge hub** - Links to documentation, KB articles, tutorials at each step +6. **Team collaboration** - Controlled authorship with shared access + +### Potential Market +- 30,000+ MSPs in North America alone +- Average MSP has 15-50 technical staff +- Adjacent markets: Internal IT teams, DevOps, Technical Support + +### Monetization Possibilities +- **Free Tier:** Personal use, limited trees +- **Pro Tier:** Team sharing, unlimited trees, analytics +- **Enterprise:** API access, SSO, custom branding, white-label +- **Marketplace:** Community-contributed trees (revenue share) +- **Consulting:** Custom tree development services +- **Integrations:** Paid add-ons for ConnectWise, Kaseya, etc. + +### Name Ideas (To Workshop) +- TroubleTree +- DecisionPath +- MSP Navigator +- FlowDoc +- DiagnosticFlow +- PathFinder +- TechTree +- TicketFlow +- DiagPath + +### Competitive Landscape +**Current Solutions:** +- Static runbooks/wiki pages (not interactive) +- Flowchart tools (not designed for real-time troubleshooting) +- Ticketing system templates (limited branching logic) +- Decision tree software (too generic, not MSP-focused) + +**Our Advantage:** +Purpose-built for technical troubleshooting with automation integration and automatic documentation generation. + +### Technology Philosophy +- **Web-first:** Accessible anywhere, no installation +- **Progressive enhancement:** Works offline, syncs when online +- **API-driven:** Backend separate from frontend for flexibility +- **Extensible:** Plugin architecture for integrations +- **Open-source friendly:** Consider open-sourcing core, monetize integrations/hosting + +### Project Status +**Current Phase:** Planning and Architecture +**Next Phase:** MVP Development (Weeks 1-3) +**Target MVP Date:** 3 weeks from project start +**Target Production Date:** 6-8 weeks from project start diff --git a/02-TECHNICAL-ARCHITECTURE.md b/02-TECHNICAL-ARCHITECTURE.md new file mode 100644 index 00000000..25a54982 --- /dev/null +++ b/02-TECHNICAL-ARCHITECTURE.md @@ -0,0 +1,541 @@ +# Technical Architecture + +## System Architecture + +### High-Level Architecture +``` +┌─────────────────────────────────────────────────────┐ +│ Frontend (React/Vue) │ +│ - Tree Navigation UI │ +│ - Tree Editor │ +│ - Session Management │ +│ - Export Functionality │ +└─────────────────┬───────────────────────────────────┘ + │ REST API / WebSocket +┌─────────────────┴───────────────────────────────────┐ +│ Backend (Python Flask/FastAPI) │ +│ - Authentication & Authorization │ +│ - Tree CRUD Operations │ +│ - Session Tracking │ +│ - Export Generation │ +│ - File Upload/Storage │ +│ - Automation Execution (Future) │ +└─────────────────┬───────────────────────────────────┘ + │ +┌─────────────────┴───────────────────────────────────┐ +│ Database (PostgreSQL) │ +│ - Trees (JSON) │ +│ - Sessions │ +│ - Users & Teams │ +│ - Attachments Metadata │ +└──────────────────────────────────────────────────────┘ + │ +┌─────────────────┴───────────────────────────────────┐ +│ Object Storage (S3/MinIO) │ +│ - Screenshots │ +│ - Command Outputs │ +│ - Logs & Attachments │ +└──────────────────────────────────────────────────────┘ +``` + +## Tech Stack + +### Frontend +**Primary Choice: React** +- **Pros:** Large ecosystem, excellent offline support (PWA), familiar to most developers +- **Alternatives:** Vue.js (simpler), Svelte (faster) +- **UI Framework:** Tailwind CSS + shadcn/ui (clean, professional look) +- **State Management:** React Context + useReducer (simple) or Zustand (if needed) +- **Routing:** React Router +- **Offline:** Service Workers + IndexedDB for offline tree caching + +### Backend +**Primary Choice: Python FastAPI** +- **Pros:** Modern, fast, async support, automatic API docs, matches Michael's learning path +- **Alternatives:** Flask (simpler but less performant), Django (heavier) +- **Authentication:** JWT tokens + httpOnly cookies +- **Validation:** Pydantic models +- **ORM:** SQLAlchemy 2.0 (async) +- **Migration:** Alembic + +### Database +**Primary Choice: PostgreSQL** +- **Pros:** JSON/JSONB support perfect for tree storage, reliable, scalable +- **Schema Design:** + - Hybrid approach: Relational for users/sessions, JSONB for tree structure + - Full-text search for tree discovery + - Indexes on frequently queried fields + +### File Storage +**Primary Choice: S3-compatible storage** +- **Development:** MinIO (self-hosted, S3-compatible) +- **Production:** AWS S3 or DigitalOcean Spaces +- **Strategy:** Pre-signed URLs for uploads, CDN for delivery + +### Hosting +**Development:** +- Frontend: Local dev server (Vite) +- Backend: Local Python server +- Database: Docker PostgreSQL + +**Production Options:** +1. **Simple Start:** Railway or Render (full-stack hosting) + - Cost: ~$10-20/month + - Pros: Easy deployment, managed databases + - Cons: Less control, potential scaling issues + +2. **Scalable:** DigitalOcean Droplets + Managed DB + - Cost: ~$30-50/month + - Pros: More control, better performance + - Cons: More maintenance + +3. **Enterprise:** AWS/Azure + - Cost: Variable + - Pros: Full feature set, enterprise compliance + - Cons: Complex, expensive + +## Data Models + +### Database Schema + +#### Users Table +```sql +CREATE TABLE users ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + email VARCHAR(255) UNIQUE NOT NULL, + password_hash VARCHAR(255) NOT NULL, + name VARCHAR(255) NOT NULL, + role VARCHAR(50) NOT NULL, -- admin, engineer, viewer + team_id UUID REFERENCES teams(id), + created_at TIMESTAMP DEFAULT NOW(), + last_login TIMESTAMP +); +``` + +#### Teams Table +```sql +CREATE TABLE teams ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT NOW() +); +``` + +#### Trees Table +```sql +CREATE TABLE trees ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name VARCHAR(255) NOT NULL, + description TEXT, + category VARCHAR(100), -- Citrix, Active Directory, Networking, etc. + tree_structure JSONB NOT NULL, -- The actual decision tree + author_id UUID REFERENCES users(id), + team_id UUID REFERENCES teams(id), + is_active BOOLEAN DEFAULT true, + version INTEGER DEFAULT 1, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + usage_count INTEGER DEFAULT 0 +); + +CREATE INDEX idx_trees_category ON trees(category); +CREATE INDEX idx_trees_team ON trees(team_id); +CREATE INDEX idx_trees_search ON trees USING gin(to_tsvector('english', name || ' ' || description)); +``` + +#### Sessions Table +```sql +CREATE TABLE sessions ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tree_id UUID REFERENCES trees(id), + user_id UUID REFERENCES users(id), + tree_snapshot JSONB NOT NULL, -- Copy of tree at time of use (for version tracking) + path_taken JSONB NOT NULL, -- Array of node_ids visited + decisions JSONB NOT NULL, -- Decisions made at each node with notes + started_at TIMESTAMP DEFAULT NOW(), + completed_at TIMESTAMP, + ticket_number VARCHAR(100), + client_name VARCHAR(255), + exported BOOLEAN DEFAULT false +); + +CREATE INDEX idx_sessions_user ON sessions(user_id); +CREATE INDEX idx_sessions_tree ON sessions(tree_id); +CREATE INDEX idx_sessions_dates ON sessions(started_at, completed_at); +``` + +#### Attachments Table +```sql +CREATE TABLE attachments ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + session_id UUID REFERENCES sessions(id), + node_id VARCHAR(100), -- Which decision node this was attached to + file_name VARCHAR(255) NOT NULL, + file_type VARCHAR(50), + file_size INTEGER, + storage_path VARCHAR(500), -- S3 key or file path + uploaded_at TIMESTAMP DEFAULT NOW() +); +``` + +### Tree Structure (JSON) + +```json +{ + "tree_id": "citrix-vda-not-registering", + "name": "Citrix VDA Not Registering", + "category": "Citrix", + "description": "Troubleshoot VDA registration issues with Delivery Controller", + "estimated_time": "10-15 minutes", + "start_node": "node_1", + "metadata": { + "author": "Michael Chihlas", + "created": "2026-01-22", + "last_updated": "2026-01-22", + "version": "1.0", + "tags": ["citrix", "vda", "registration", "delivery-controller"] + }, + "nodes": { + "node_1": { + "id": "node_1", + "type": "decision", + "question": "Can you ping the VDA from the Delivery Controller?", + "help_text": "From DDC, open PowerShell and run: Test-Connection -ComputerName VDA-HOSTNAME -Count 4", + "documentation_links": [ + { + "title": "Citrix VDA Communication Requirements", + "url": "https://docs.citrix.com/..." + } + ], + "decision_type": "yes_no", + "allow_notes": true, + "allow_attachments": true, + "allow_custom_branch": true, + "yes": "node_2", + "no": "node_network_issue" + }, + "node_2": { + "id": "node_2", + "type": "decision", + "question": "What is the status of the Citrix Virtual Desktop Agent service?", + "help_text": "Run: Get-Service -Name 'BrokerAgent' | Select-Object Status, StartType", + "decision_type": "multiple_choice", + "allow_notes": true, + "options": [ + { + "label": "Running", + "value": "running", + "next": "node_check_broker" + }, + { + "label": "Stopped", + "value": "stopped", + "next": "node_start_service" + }, + { + "label": "Stuck in Starting/Stopping", + "value": "stuck", + "next": "node_service_stuck" + } + ] + }, + "node_check_broker": { + "id": "node_check_broker", + "type": "action", + "title": "Check Broker Connection", + "instruction": "Verify the VDA can communicate with the Delivery Controller on port 80/443", + "commands": [ + "Test-NetConnection -ComputerName DDC-HOSTNAME -Port 80", + "Test-NetConnection -ComputerName DDC-HOSTNAME -Port 443" + ], + "documentation_links": [ + { + "title": "VDA to DDC Communication Ports", + "url": "https://docs.citrix.com/..." + } + ], + "automation_available": false, + "next": "node_3" + }, + "node_start_service": { + "id": "node_start_service", + "type": "action", + "title": "Start Citrix VDA Service", + "instruction": "Start the Citrix Virtual Desktop Agent service and check for errors", + "commands": [ + "Start-Service -Name 'BrokerAgent'", + "Get-Service -Name 'BrokerAgent' | Select-Object Status" + ], + "automation_available": true, + "automation": { + "type": "powershell", + "script_id": "start-citrix-vda-service", + "description": "Starts Citrix VDA service with error handling", + "requires_elevation": true, + "parameters": [] + }, + "next": "node_verify_registration" + }, + "node_verify_registration": { + "id": "node_verify_registration", + "type": "decision", + "question": "Is the VDA now showing as registered in Citrix Studio?", + "help_text": "Open Citrix Studio > Machine Catalogs > Check VDA status", + "decision_type": "yes_no", + "yes": "node_resolved", + "no": "node_check_event_viewer" + }, + "node_resolved": { + "id": "node_resolved", + "type": "resolution", + "title": "Issue Resolved", + "summary": "VDA successfully registered with Delivery Controller", + "resolution_notes": "Document the specific action that resolved the issue in the notes above." + }, + "node_network_issue": { + "id": "node_network_issue", + "type": "branch", + "title": "Network Connectivity Issue Detected", + "description": "Unable to ping VDA from DDC - this is a network/firewall issue", + "suggested_next_tree": "network-connectivity-troubleshooting", + "manual_steps": [ + "Check if VDA is powered on", + "Verify network cable is connected", + "Check firewall rules between DDC and VDA", + "Verify DNS resolution for VDA hostname" + ] + } + } +} +``` + +### Session Data Structure + +```json +{ + "session_id": "abc-123", + "tree_id": "citrix-vda-not-registering", + "tree_snapshot": { /* full tree at time of use */ }, + "user_id": "user-456", + "started_at": "2026-01-22T14:30:00Z", + "completed_at": "2026-01-22T14:45:00Z", + "ticket_number": "INC-12345", + "client_name": "City of Warner Robins", + "path_taken": [ + "node_1", + "node_2", + "node_start_service", + "node_verify_registration", + "node_resolved" + ], + "decisions": [ + { + "node_id": "node_1", + "question": "Can you ping the VDA from the Delivery Controller?", + "answer": "yes", + "notes": "Ping successful, 2ms response time, no packet loss", + "timestamp": "2026-01-22T14:31:00Z", + "attachments": [] + }, + { + "node_id": "node_2", + "question": "What is the status of the Citrix Virtual Desktop Agent service?", + "answer": "stopped", + "notes": "Service was stopped. Checking dependencies - NetLogon service also stopped.", + "timestamp": "2026-01-22T14:33:00Z", + "attachments": ["attachment-id-789"] + }, + { + "node_id": "node_start_service", + "action_performed": "Started Citrix VDA service", + "notes": "Started NetLogon first, then BrokerAgent. Both services now running.", + "automation_used": false, + "timestamp": "2026-01-22T14:40:00Z", + "attachments": [] + }, + { + "node_id": "node_verify_registration", + "question": "Is the VDA now showing as registered in Citrix Studio?", + "answer": "yes", + "notes": "VDA shows as 'Registered' in Studio. User able to launch session successfully.", + "timestamp": "2026-01-22T14:44:00Z", + "attachments": ["screenshot-registration.png"] + } + ] +} +``` + +## API Endpoints + +### Authentication +``` +POST /api/auth/register - Register new user +POST /api/auth/login - Login +POST /api/auth/logout - Logout +GET /api/auth/me - Get current user +POST /api/auth/refresh - Refresh JWT token +``` + +### Trees +``` +GET /api/trees - List all trees (with filters) +GET /api/trees/:id - Get specific tree +POST /api/trees - Create new tree (admin/engineer only) +PUT /api/trees/:id - Update tree (admin/engineer only) +DELETE /api/trees/:id - Soft delete tree (admin only) +GET /api/trees/categories - List all categories +GET /api/trees/search - Full-text search trees +``` + +### Sessions +``` +GET /api/sessions - List user's sessions +GET /api/sessions/:id - Get specific session +POST /api/sessions - Start new troubleshooting session +PUT /api/sessions/:id - Update session (add decisions/notes) +POST /api/sessions/:id/complete - Mark session as complete +POST /api/sessions/:id/export - Export session to formatted notes +``` + +### Attachments +``` +POST /api/sessions/:id/attachments - Upload attachment +GET /api/sessions/:id/attachments - List attachments +GET /api/attachments/:id - Get attachment +DELETE /api/attachments/:id - Delete attachment +``` + +### Teams (Phase 2) +``` +GET /api/teams - List teams +POST /api/teams - Create team (admin only) +GET /api/teams/:id/members - List team members +POST /api/teams/:id/members - Add team member +DELETE /api/teams/:id/members/:user_id - Remove team member +``` + +### Analytics (Phase 3) +``` +GET /api/analytics/trees/:id/usage - Tree usage statistics +GET /api/analytics/trees/:id/paths - Common paths taken +GET /api/analytics/team/performance - Team troubleshooting metrics +GET /api/analytics/user/history - User's troubleshooting history +``` + +### Automation (Phase 4) +``` +GET /api/automation/scripts - List available automation scripts +POST /api/automation/execute - Execute automation script +GET /api/automation/history - Automation execution history +``` + +## Security Considerations + +### Authentication & Authorization +- JWT tokens with short expiry (15 min access, 7 day refresh) +- Role-based access control (RBAC) +- Password requirements: min 10 chars, complexity +- Rate limiting on auth endpoints +- Account lockout after failed attempts + +### Data Protection +- All passwords hashed with bcrypt (cost factor 12) +- Sensitive data encrypted at rest +- HTTPS only in production +- CORS properly configured +- SQL injection prevention (parameterized queries) +- XSS prevention (input sanitization, CSP headers) + +### File Upload Security +- File type validation (whitelist only) +- File size limits (10MB per file) +- Virus scanning (ClamAV integration for Phase 3) +- Separate storage domain (prevent XSS via uploads) +- Signed URLs with expiration + +### API Security +- Rate limiting (100 requests/min per user) +- Request size limits +- API versioning (/api/v1/...) +- Audit logging for sensitive operations + +## Performance Considerations + +### Database +- Indexes on frequently queried fields +- Connection pooling +- Query optimization (EXPLAIN ANALYZE) +- Consider read replicas for Phase 3+ + +### Caching Strategy +- Redis for session storage (Phase 2) +- Cache frequently accessed trees +- CDN for static assets +- Browser caching headers + +### Frontend Performance +- Code splitting (lazy load routes) +- Tree data cached in IndexedDB +- Debounced search inputs +- Virtualized lists for large datasets +- Optimistic UI updates + +## Monitoring & Observability + +### Logging +- Structured logging (JSON format) +- Log levels: DEBUG, INFO, WARNING, ERROR, CRITICAL +- Request ID tracking across services +- User action auditing + +### Metrics (Phase 3) +- API response times +- Database query performance +- Error rates +- User engagement metrics +- System resource usage + +### Error Tracking +- Sentry integration for error tracking +- User-friendly error messages +- Automatic error reporting with context + +## Deployment Strategy + +### CI/CD Pipeline +1. **Development:** Local development with hot reload +2. **Testing:** Automated tests on PR +3. **Staging:** Auto-deploy to staging environment +4. **Production:** Manual approval → deploy + +### Database Migrations +- Alembic for schema migrations +- Backwards-compatible changes +- Rollback capability +- Test migrations on staging first + +### Backup Strategy +- Automated daily database backups +- Point-in-time recovery capability +- File storage replication +- Backup retention: 30 days + +## Future Technical Considerations + +### Scalability +- Horizontal scaling (multiple app servers) +- Database sharding (by team_id) +- Microservices architecture (if needed) +- Message queue for async tasks (Celery + Redis) + +### Mobile Apps +- React Native for iOS/Android +- Shared API backend +- Offline-first architecture +- Push notifications for team updates + +### AI/ML Integration (Phase 5+) +- Suggest next steps based on past sessions +- Auto-categorize tickets +- Predict resolution time +- Natural language tree navigation diff --git a/03-DEVELOPMENT-ROADMAP.md b/03-DEVELOPMENT-ROADMAP.md new file mode 100644 index 00000000..4d000912 --- /dev/null +++ b/03-DEVELOPMENT-ROADMAP.md @@ -0,0 +1,431 @@ +# Development Roadmap + +## Phase 1: MVP (Weeks 1-3) +**Goal:** Get Michael using this on real tickets ASAP + +### Week 1: Foundation +**Backend:** +- [ ] Project setup (Python FastAPI, project structure) +- [ ] Database schema design and creation (PostgreSQL) +- [ ] User authentication (register, login, JWT tokens) +- [ ] Basic CRUD API for trees +- [ ] Session tracking API + +**Frontend:** +- [ ] Project setup (React + Vite, Tailwind CSS) +- [ ] Authentication UI (login, register) +- [ ] Basic layout and navigation +- [ ] Tree selection/browsing interface + +**DevOps:** +- [ ] Docker setup for local development +- [ ] Database migrations (Alembic) +- [ ] Environment configuration + +### Week 2: Core Functionality +**Backend:** +- [ ] Session decision tracking +- [ ] Export API (plain text, markdown, HTML) +- [ ] File upload API (basic) +- [ ] Tree retrieval optimizations + +**Frontend:** +- [ ] Tree navigation interface + - [ ] Display questions/decisions + - [ ] Yes/No buttons + - [ ] Multiple choice options + - [ ] Notes input at each step + - [ ] Back button (undo decision) + - [ ] Progress indicator +- [ ] Session management + - [ ] Start new session + - [ ] Save progress + - [ ] Complete session +- [ ] Export functionality + - [ ] Preview export + - [ ] Copy to clipboard + - [ ] Download as file + +**Content:** +- [ ] Create 5 starter decision trees: + 1. Citrix VDA Not Registering + 2. FSLogix Profile Issues + 3. Active Directory Replication Failure + 4. SonicWall VPN Tunnel Down + 5. User Unable to Access File Share + +### Week 3: Polish & Testing +**Backend:** +- [ ] Error handling improvements +- [ ] API documentation (automatic via FastAPI) +- [ ] Performance optimization + +**Frontend:** +- [ ] UI/UX refinements +- [ ] Responsive design (desktop focus) +- [ ] Loading states +- [ ] Error handling and user feedback +- [ ] Keyboard shortcuts + +**Testing:** +- [ ] Michael tests on 5-10 real tickets +- [ ] Bug fixes based on feedback +- [ ] Documentation updates + +**Deployment:** +- [ ] Deploy to Railway/Render +- [ ] Setup production database +- [ ] Configure environment variables +- [ ] SSL/HTTPS setup + +### MVP Success Criteria +- [ ] Michael can log in +- [ ] Michael can navigate through a decision tree +- [ ] Michael can add notes at each step +- [ ] Michael can export clean, formatted notes +- [ ] Notes make sense and show work performed +- [ ] System is stable and responsive +- [ ] Michael actually uses it for real tickets + +--- + +## Phase 2: Team-Ready (Weeks 4-6) + +### Week 4: Team Features +**Backend:** +- [ ] Team model and API +- [ ] Role-based access control (admin, engineer, viewer) +- [ ] Tree authorship controls (who can edit) +- [ ] Multi-user session tracking + +**Frontend:** +- [ ] Team management UI +- [ ] User role display +- [ ] Tree editor permissions +- [ ] User profile page + +**Content:** +- [ ] Add 5-10 more decision trees based on Michael's feedback +- [ ] Refine existing trees based on usage data + +### Week 5: Tree Management +**Backend:** +- [ ] Tree categories and tagging +- [ ] Tree search API (full-text) +- [ ] Tree usage statistics +- [ ] Session history API + +**Frontend:** +- [ ] Tree editor UI + - [ ] Visual tree builder (drag-and-drop nodes) + - [ ] Add/edit/delete nodes + - [ ] Set question types (yes/no, multiple choice, action) + - [ ] Add help text and documentation links + - [ ] Save and publish +- [ ] Tree library/browser + - [ ] Category filters + - [ ] Search functionality + - [ ] Sort by usage, date, name +- [ ] Session history viewer + - [ ] List past sessions + - [ ] View session details + - [ ] Re-export past sessions + +### Week 6: Mobile & Polish +**Frontend:** +- [ ] Mobile-responsive design + - [ ] Touch-friendly buttons + - [ ] Optimized layouts for small screens + - [ ] Test on iOS and Android +- [ ] Custom branches during navigation + - [ ] "Add custom step" button + - [ ] Quick branch creation + - [ ] Custom branches saved to session (not tree) +- [ ] UI improvements based on feedback + +**Testing:** +- [ ] Onboard 2-3 team members +- [ ] Gather feedback +- [ ] Bug fixes and refinements + +### Phase 2 Success Criteria +- [ ] 3-5 engineers actively using the tool +- [ ] Tree editor is functional and intuitive +- [ ] Mobile interface is usable +- [ ] Custom branches work smoothly +- [ ] Team reports positive feedback + +--- + +## Phase 3: Professional Tool (Weeks 7-12) + +### Week 7-8: File Attachments +**Backend:** +- [ ] S3-compatible storage setup (MinIO/DigitalOcean Spaces) +- [ ] File upload with validation (type, size) +- [ ] Generate pre-signed URLs +- [ ] Attachment metadata storage +- [ ] Include attachments in exports + +**Frontend:** +- [ ] File upload UI at decision nodes + - [ ] Drag-and-drop upload + - [ ] Screenshot paste support (Ctrl+V) + - [ ] File preview (images) + - [ ] Multiple files per node +- [ ] Attachment gallery in session view +- [ ] Download attachments + +### Week 9-10: Advanced Features +**Backend:** +- [ ] Offline data sync API +- [ ] Client-specific context storage +- [ ] Advanced export templates +- [ ] Tree analytics API + +**Frontend:** +- [ ] Offline capability (Service Workers + IndexedDB) + - [ ] Cache trees locally + - [ ] Queue decisions while offline + - [ ] Auto-sync when online + - [ ] Offline indicator +- [ ] Client context system + - [ ] Save client-specific details (server names, topologies) + - [ ] Auto-populate known values + - [ ] Client selection at session start +- [ ] Export enhancements + - [ ] PDF export + - [ ] Custom export templates + - [ ] Include/exclude attachments + - [ ] Format for specific ticket systems + +### Week 11-12: Analytics & Optimization +**Backend:** +- [ ] Tree usage analytics +- [ ] Common paths analysis +- [ ] Team performance metrics +- [ ] Automation framework foundation + +**Frontend:** +- [ ] Analytics dashboard + - [ ] Tree usage stats + - [ ] Most common paths + - [ ] Average completion time + - [ ] Success rates +- [ ] "Send to engineer" feature + - [ ] Generate simplified checklist + - [ ] Share via link (read-only) + - [ ] Print-friendly format +- [ ] Performance optimizations + - [ ] Lazy loading + - [ ] Code splitting + - [ ] Image optimization + +**Documentation:** +- [ ] User guide +- [ ] Tree creation best practices +- [ ] Video tutorials (basic) + +### Phase 3 Success Criteria +- [ ] Attachments work reliably +- [ ] Offline mode functions correctly +- [ ] Exports are professional and comprehensive +- [ ] Analytics provide useful insights +- [ ] Tool feels polished and complete +- [ ] Michael using it for 50%+ of tickets + +--- + +## Phase 4: MSP Platform (Months 4-6) + +### Month 4: API & Integrations +**Backend:** +- [ ] Public API design (versioned) +- [ ] API key management +- [ ] Webhook system +- [ ] Rate limiting +- [ ] API documentation (OpenAPI/Swagger) + +**Integrations:** +- [ ] ConnectWise PSA integration (Phase 4a) + - [ ] Create tickets from sessions + - [ ] Sync ticket numbers + - [ ] Update ticket notes +- [ ] Kaseya integration (Phase 4b) +- [ ] LabTech/Automate integration (Phase 4c) + +**Frontend:** +- [ ] API key management UI +- [ ] Webhook configuration UI +- [ ] Integration status dashboard + +### Month 5: Automation Integration +**Backend:** +- [ ] PowerShell script execution framework +- [ ] Parameter handling and validation +- [ ] Output capture and logging +- [ ] Security sandbox (execution limits) +- [ ] Script library management + +**Frontend:** +- [ ] Automation toggle at action nodes +- [ ] Script parameter input +- [ ] Execution progress indicator +- [ ] Output display +- [ ] Error handling UI + +**Content:** +- [ ] Create automation scripts for common tasks: + - [ ] Restart Citrix services + - [ ] Clear FSLogix cache + - [ ] Test AD replication + - [ ] VPN tunnel diagnostics + - [ ] Network connectivity tests + +### Month 6: Enterprise Features +**Backend:** +- [ ] Tree versioning system +- [ ] Tree approval workflow +- [ ] Advanced RBAC +- [ ] SSO integration (SAML/OAuth) +- [ ] Audit logging +- [ ] White-label configuration + +**Frontend:** +- [ ] Tree version history +- [ ] Approval request UI +- [ ] Advanced permission management +- [ ] White-label theme customization +- [ ] Comprehensive admin panel + +**Infrastructure:** +- [ ] Multi-tenancy support +- [ ] Data isolation +- [ ] Scalability improvements +- [ ] Backup and recovery automation + +### Phase 4 Success Criteria +- [ ] API is functional and documented +- [ ] At least 1 major PSA integration works +- [ ] Automation executes reliably and safely +- [ ] Enterprise features meet basic needs +- [ ] System scales to 50+ concurrent users + +--- + +## Phase 5: Growth & Enhancement (Months 7-12) + +### Marketplace Development +- [ ] Community tree sharing +- [ ] Tree rating and review system +- [ ] Tree submission process +- [ ] Revenue sharing model +- [ ] Quality control process + +### Advanced Analytics +- [ ] Predictive analytics (suggest next steps) +- [ ] Pattern recognition (common failure modes) +- [ ] Resolution time predictions +- [ ] Team benchmarking + +### AI Integration +- [ ] Natural language tree navigation +- [ ] Auto-suggest tree improvements +- [ ] Intelligent documentation generation +- [ ] Anomaly detection + +### Mobile Apps +- [ ] React Native app development +- [ ] App store deployment (iOS/Android) +- [ ] Push notifications +- [ ] Offline-first architecture + +### Additional Integrations +- [ ] Microsoft 365 integration +- [ ] Slack integration (notifications, commands) +- [ ] Teams integration +- [ ] ServiceNow integration +- [ ] Jira Service Management integration + +--- + +## Long-Term Vision (Year 2+) + +### Product Evolution +- Self-learning system (trees improve automatically based on usage) +- Voice-guided troubleshooting (hands-free operation) +- AR/VR support (on-site equipment identification) +- AI co-pilot (real-time suggestions during troubleshooting) + +### Market Expansion +- Vertical-specific tree libraries (healthcare IT, financial services, education) +- Certification program for tree authors +- Professional services (custom tree development) +- Training and consultation services + +### Platform Maturity +- 99.9% uptime SLA +- Global CDN deployment +- Advanced compliance (SOC 2, ISO 27001) +- Enterprise support tiers +- White-glove onboarding + +--- + +## Risk Mitigation + +### Technical Risks +| Risk | Impact | Likelihood | Mitigation | +|------|--------|------------|------------| +| Database performance issues | High | Medium | Proper indexing, query optimization, monitoring | +| File storage costs | Medium | High | Compression, retention policies, tiered storage | +| Offline sync conflicts | Medium | Medium | Conflict resolution UI, clear sync indicators | +| Automation security | High | Low | Sandboxing, strict validation, audit logging | + +### Product Risks +| Risk | Impact | Likelihood | Mitigation | +|------|--------|------------|------------| +| Users don't adopt tool | High | Medium | Focus on ease of use, quick value demonstration | +| Trees become outdated | Medium | High | Usage analytics, update reminders, version control | +| Tree creation is too complex | High | Medium | Simple editor, templates, guided creation wizard | +| Exports don't fit workflows | High | Low | Customizable templates, user feedback loop | + +### Business Risks +| Risk | Impact | Likelihood | Mitigation | +|------|--------|------------|------------| +| Competition from established tools | Medium | Medium | Focus on MSP-specific features, tight integrations | +| Hosting costs exceed revenue | High | Low | Efficient architecture, pricing that covers costs | +| Security breach | High | Low | Security best practices, regular audits, insurance | +| Key person dependency (Michael) | Medium | High | Documentation, involve team early, open communication | + +--- + +## Success Metrics by Phase + +### Phase 1 (MVP) +- Michael uses tool for 5+ tickets in week 3 +- Export quality rated 8/10 or higher +- Zero critical bugs after week 2 + +### Phase 2 (Team-Ready) +- 3-5 engineers onboarded +- 20+ trees created +- Average 10+ sessions per day + +### Phase 3 (Professional Tool) +- 50+ sessions per week +- Offline mode used successfully +- Export time < 30 seconds +- User satisfaction 8/10+ + +### Phase 4 (MSP Platform) +- At least 1 PSA integration in production +- 5+ automation scripts available +- API used by external developers +- 100+ active users + +### Phase 5 (Growth) +- Marketplace launched with 10+ community trees +- Mobile app released +- AI features in beta +- 500+ active users diff --git a/04-FEATURE-SPECIFICATIONS.md b/04-FEATURE-SPECIFICATIONS.md new file mode 100644 index 00000000..3a671fa3 --- /dev/null +++ b/04-FEATURE-SPECIFICATIONS.md @@ -0,0 +1,606 @@ +# Feature Specifications + +## Core Features (MVP) + +### 1. Tree Navigation Interface + +**Description:** The primary interface where engineers work through troubleshooting decision trees in real-time. + +**User Flow:** +1. User selects a tree from library +2. Optionally enters ticket number and client name +3. System displays first question/decision point +4. User makes selection and optionally adds notes +5. System progresses to next appropriate node +6. Process repeats until resolution or manual exit +7. User exports completed session + +**UI Components:** +- **Question Display:** Large, clear text showing current decision point +- **Decision Options:** + - Yes/No buttons (for binary decisions) + - Multiple choice buttons (for multi-option decisions) + - Action confirmation button (for action steps) +- **Notes Field:** Expandable text area for adding context +- **Navigation:** + - Back button (undo last decision) + - Progress indicator (X of Y steps, or visual tree progress) + - Exit/Save button +- **Help Panel:** Collapsible sidebar with: + - Help text for current node + - Suggested commands/scripts + - Links to documentation + - Option to attach files + +**Technical Requirements:** +- State management for current position in tree +- Undo functionality (maintain decision history) +- Auto-save progress every 30 seconds +- Handle browser refresh gracefully +- Support keyboard navigation (arrow keys, enter, esc) + +**Edge Cases:** +- Network disconnection during session → Save locally, sync when reconnected +- User closes browser mid-session → Recover session on return +- Tree structure changes while session active → Use snapshot of tree +- Circular tree logic → Detect and prevent infinite loops + +--- + +### 2. Documentation Links Integration + +**Description:** Contextual documentation and resource links embedded at each decision node. + +**Features:** +- **Multiple Link Types:** + - Vendor documentation (Microsoft Learn, Citrix docs, etc.) + - Internal KB articles (wiki/Confluence links) + - Video tutorials (YouTube, internal training) + - Related troubleshooting trees +- **Display:** + - Show as expandable "Resources" section + - Icon indicating link type (📄 doc, 🎥 video, 🌲 tree) + - Open in new tab/window + - Preview tooltip on hover +- **Management:** + - Add/edit links in tree editor + - Track link clicks (analytics) + - Validate URLs (check for 404s) + - Support internal and external links + +**Data Structure:** +```json +"documentation_links": [ + { + "title": "Citrix VDA Communication Requirements", + "url": "https://docs.citrix.com/...", + "type": "vendor_docs", + "description": "Official Citrix documentation on VDA-DDC communication" + }, + { + "title": "Internal: VDA Troubleshooting Guide", + "url": "https://wiki.company.com/...", + "type": "internal_kb" + }, + { + "title": "Video: VDA Registration Deep Dive", + "url": "https://youtube.com/...", + "type": "video", + "duration": "8:32" + } +] +``` + +**UI Considerations:** +- Don't clutter main interface - use expandable sections +- Allow quick copy of URLs +- Indicate when link was last verified +- Option to suggest new links (crowdsourcing) + +--- + +### 3. Export Functionality + +**Description:** Generate professional, formatted documentation from troubleshooting sessions. + +**Export Formats:** + +#### Plain Text +``` +Ticket #12345 - Citrix VDA Not Registering +Troubleshooting Path: Citrix VDA Diagnostics +Technician: Michael Chihlas +Date: 2026-01-22 14:30 +Client: City of Warner Robins + +STEPS PERFORMED: +1. Network Connectivity Check + - Can ping VDA from Delivery Controller: YES + - Note: Response time 2ms, no packet loss + +2. Service Status Verification + - Citrix Virtual Desktop Agent service status: STOPPED + +3. Service Recovery + - Started Citrix VDA service + - Note: Service had stopped due to NetLogon dependency failure + +4. Registration Verification + - VDA showing as registered in Studio: YES + - Note: User able to launch session successfully + +RESOLUTION: +Started Citrix VDA service after resolving NetLogon dependency issue. +VDA successfully registered with Delivery Controller. + +ATTACHED FILES: +- eventvwr_screenshot.png +- service_status_before.txt + +TIME SPENT: 15 minutes +``` + +#### Markdown +```markdown +# Ticket #12345 - Citrix VDA Not Registering + +**Troubleshooting Path:** Citrix VDA Diagnostics +**Technician:** Michael Chihlas +**Date:** 2026-01-22 14:30 +**Client:** City of Warner Robins + +## Steps Performed + +### 1. Network Connectivity Check +✓ Can ping VDA from Delivery Controller: **YES** + +> Response time 2ms, no packet loss + +### 2. Service Status Verification +✗ Citrix Virtual Desktop Agent service status: **STOPPED** + +### 3. Service Recovery +✓ Started Citrix VDA service + +> Service had stopped due to NetLogon dependency failure + +### 4. Registration Verification +✓ VDA showing as registered in Studio: **YES** + +> User able to launch session successfully + +## Resolution +Started Citrix VDA service after resolving NetLogon dependency issue. VDA successfully registered with Delivery Controller. + +## Attached Files +- [eventvwr_screenshot.png](link) +- [service_status_before.txt](link) + +--- +**Time Spent:** 15 minutes +``` + +#### HTML +- Styled version of markdown +- Embeds images inline +- Collapsible sections +- Print-friendly CSS +- Copy button for easy paste into web forms + +**Customization Options:** +- Include/exclude sections (steps, resolution, attachments, time) +- Custom header/footer +- Branding/logo +- Date/time format +- Technician name format +- Template selection (different formats for different ticket systems) + +**Technical Implementation:** +- Backend template engine (Jinja2) +- Frontend preview before export +- Copy to clipboard functionality +- Download as file +- Send via email (Phase 3) + +--- + +### 4. Tree Editor + +**Description:** Visual interface for creating and editing decision trees. + +**Editing Modes:** +- **Visual Mode:** Drag-and-drop node editor (like flowchart) +- **Form Mode:** Fill-in-the-blanks forms for each node +- **JSON Mode:** Direct JSON editing (for advanced users) + +**Node Types:** +1. **Decision Node:** Question with multiple possible answers +2. **Action Node:** Instruction to perform a task +3. **Resolution Node:** Terminal node indicating issue resolved +4. **Branch Node:** Link to another tree or manual escalation + +**Node Configuration:** +- **Common Fields:** + - Node ID (auto-generated, editable) + - Node type + - Title/Question text + - Help text (optional) + - Documentation links (optional) + - Allow notes (toggle) + - Allow attachments (toggle) + - Allow custom branch (toggle) + +- **Decision Node Specific:** + - Decision type (yes/no, multiple choice, numeric range) + - Options and next node for each + +- **Action Node Specific:** + - Action instructions + - Commands/scripts to run + - Expected outcome + - Automation available (toggle) + - Automation configuration + - Next node + +- **Resolution Node Specific:** + - Resolution summary template + - Success criteria + - Follow-up recommendations + +**Tree Metadata:** +- Tree name +- Description +- Category/tags +- Estimated time to complete +- Difficulty level +- Author +- Version +- Creation/update dates + +**Validation:** +- No orphaned nodes (every node reachable from start) +- No circular references (detect loops) +- Every decision has valid next nodes +- Terminal nodes exist +- Required fields filled + +**UI Features:** +- Auto-save draft +- Preview mode (test the tree) +- Duplicate tree (create variations) +- Import/export tree JSON +- Publish (make active) vs. Draft +- Version history (Phase 3) + +--- + +### 5. Attachment Support + +**Description:** Upload and attach files (screenshots, logs, command outputs) to specific decision nodes during troubleshooting. + +**Supported File Types:** +- Images: PNG, JPG, GIF, WebP +- Documents: TXT, LOG, CSV, JSON, XML +- Screenshots: Direct paste from clipboard (Ctrl+V) +- Archives: ZIP (for bundled logs) + +**File Size Limits:** +- Individual file: 10 MB +- Per session: 50 MB +- Total per user: 500 MB (soft limit, alert before reaching) + +**Upload Methods:** +1. **File Picker:** Click to browse and select +2. **Drag & Drop:** Drag files onto upload area +3. **Paste:** Ctrl+V to paste screenshot from clipboard +4. **Command Output:** Copy-paste terminal output, save as .txt + +**Features:** +- **Preview:** Thumbnail for images, icon for others +- **Annotation:** Add caption/description to each file +- **Organize:** Files associated with specific decision node +- **Download:** Download individual files or all as ZIP +- **Delete:** Remove unwanted attachments +- **Reference in Notes:** Auto-reference in export ("See attached screenshot") + +**Storage:** +- S3-compatible object storage +- Organized by session_id/node_id/filename +- Pre-signed URLs for secure access +- Automatic cleanup of old sessions (retention policy) + +**Export Integration:** +- List attached files in export +- Include inline images in HTML/PDF exports +- Provide download links in text/markdown exports +- Option to bundle as ZIP with export + +**Technical Considerations:** +- Virus scanning (Phase 3) +- Content-Type validation +- Generate thumbnails for images +- Compress images to reduce storage +- CDN for faster delivery + +--- + +## Advanced Features (Phase 3+) + +### 6. Automation Integration + +**Description:** Execute PowerShell scripts or commands directly from action nodes, capturing output and integrating into troubleshooting flow. + +**Architecture:** +``` +Frontend → Backend API → Automation Worker → PowerShell/Script → Return Output +``` + +**Safety Mechanisms:** +- **Execution Sandbox:** Isolated environment, limited permissions +- **Timeout:** Max execution time (5 minutes default) +- **Resource Limits:** CPU, memory, network constraints +- **Approval Required:** Sensitive scripts need admin approval +- **Audit Log:** Every execution logged with user, timestamp, parameters, output + +**Script Types:** +1. **Read-Only:** Get information (Get-Service, Test-Connection) +2. **Safe Actions:** Restart services, clear caches +3. **Configuration Changes:** Modify settings (require extra confirmation) + +**User Experience:** +- **Option at Action Node:** "Run Automation" vs. "Manual" buttons +- **Parameter Input:** If script needs parameters (hostname, username) +- **Progress Indicator:** Show script is running +- **Output Display:** Show script output in real-time or after completion +- **Error Handling:** Clear error messages, suggest manual steps +- **Capture Output:** Automatically save output as attachment + +**Script Management:** +- **Script Library:** Centralized repository of approved scripts +- **Versioning:** Track script versions +- **Testing:** Test mode before production use +- **Permissions:** Control who can run which scripts +- **Documentation:** Each script has description, parameters, example usage + +**Integration with ChihlasChecker:** +- Import existing PowerShell scripts +- Wrap scripts with parameter handling +- Standardize output format +- Error code mapping + +**Example Script Configuration:** +```json +{ + "script_id": "restart-citrix-vda-service", + "name": "Restart Citrix VDA Service", + "description": "Restarts the Citrix Virtual Desktop Agent service and its dependencies", + "type": "powershell", + "requires_elevation": true, + "timeout_seconds": 300, + "parameters": [ + { + "name": "ComputerName", + "type": "string", + "required": true, + "description": "VDA hostname or IP address", + "validation": "^[a-zA-Z0-9.-]+$" + } + ], + "script": "path/to/script.ps1", + "risk_level": "medium", + "approval_required": false +} +``` + +--- + +### 7. Offline Mode + +**Description:** Continue using the application without internet connectivity, with automatic sync when connection restored. + +**Offline Capabilities:** +- Navigate cached trees +- Add notes and make decisions +- Queue file attachments (upload when online) +- View past sessions (cached) + +**What Requires Online:** +- Creating new trees +- Sharing with team +- Accessing uncached trees +- Running automation +- Real-time collaboration + +**Technical Implementation:** +- **Service Worker:** Intercept network requests, serve from cache +- **IndexedDB:** Store trees, sessions, user data locally +- **Sync Queue:** Track changes made offline, sync when online +- **Conflict Resolution:** Handle conflicts if tree was updated while offline + +**User Experience:** +- **Status Indicator:** Clear online/offline badge +- **Sync Status:** "Syncing..." indicator when uploading changes +- **Cache Management:** Settings to control what's cached +- **Manual Sync:** Button to force sync attempt + +**Data Synchronization:** +1. User goes offline mid-session +2. Continues making decisions, adding notes (stored locally) +3. Attempts to attach file (queued for upload) +4. User regains connection +5. System detects online status +6. Uploads queued attachments +7. Syncs session data to server +8. Resolves any conflicts +9. Updates local cache with server changes + +**Conflict Resolution:** +- Last-write-wins for session data (sessions are user-specific) +- Alert user if tree structure changed significantly +- Option to review changes before syncing + +--- + +### 8. Client Context System + +**Description:** Store and auto-populate client-specific information to speed up troubleshooting and reduce repetitive data entry. + +**Stored Context:** +- **Infrastructure Details:** + - Server names (DC names, Citrix DDC, file servers) + - IP addresses and subnets + - Domain names + - VPN configurations +- **Common Issues:** + - Known recurring problems + - Specific quirks of client environment +- **Contact Information:** + - Primary contacts + - Escalation paths +- **Documentation Links:** + - Client-specific network diagrams + - Password vault links + - Configuration management links + +**Features:** +- **Client Selection:** Dropdown or search to select client at session start +- **Auto-Population:** When client selected, pre-fill known values in tree +- **Context Cards:** Display relevant client info in sidebar during troubleshooting +- **Quick Edit:** Update client context without leaving session +- **Privacy:** Sensitive data encrypted, access controlled + +**Example Usage:** +1. User starts session for "City of Warner Robins" +2. System loads Warner Robins context +3. When tree asks "What is the primary domain controller?", it pre-fills "WR-DC01" +4. User can accept or override +5. Saves time and reduces errors + +**Data Structure:** +```json +{ + "client_id": "warner-robins-city", + "client_name": "City of Warner Robins", + "context": { + "domain_controllers": ["WR-DC01.wrga.local", "WR-DC02.wrga.local"], + "citrix_ddc": ["WR-CTX01.wrga.local"], + "file_servers": ["WR-FS01.wrga.local"], + "subnets": ["10.20.0.0/16"], + "vpn_gateway": "vpn.wrga.gov", + "primary_contact": "John Smith - IT Director", + "escalation": "jane.doe@wrga.gov", + "notes": "Always check VPN tunnel to courthouse first - frequently drops" + } +} +``` + +--- + +### 9. "Send to Engineer" Feature + +**Description:** Generate a simplified, read-only view or checklist from a decision tree that can be shared with onsite engineers or junior staff. + +**Use Cases:** +- Remote engineer creates troubleshooting plan +- Shares link with onsite tech +- Onsite tech follows steps, reports results +- Remote engineer updates plan if needed + +**Features:** +- **Simplified View:** Streamlined UI, just the steps +- **Checklist Mode:** Check off completed steps +- **Print-Friendly:** Clean layout for printing +- **Share Link:** Generate unique URL, no login required (optional) +- **QR Code:** For easy mobile access +- **Expiration:** Links expire after X hours/days +- **Password Protection:** Optional password for sensitive clients + +**Generated Content:** +``` +Warner Robins - VDA Registration Issue +Generated by: Michael Chihlas +Date: 2026-01-22 + +[ ] Step 1: Check network connectivity + From DDC, run: ping WR-VDA05 + Expected: Replies with <5ms latency + +[ ] Step 2: Verify VDA service status + Run: Get-Service BrokerAgent + Expected: Status = Running + +[ ] Step 3: If service stopped, start it + Run: Start-Service BrokerAgent + Wait 30 seconds + +[ ] Step 4: Verify registration + Open Citrix Studio + Check Machine Catalogs + Expected: WR-VDA05 shows "Registered" + +Notes/Results: +[Space for onsite tech to write notes] + +Contact remote engineer if issues: michael@msp.com +``` + +**Technical Implementation:** +- Generate static HTML page from tree +- Store in temporary location (S3) +- Track access (analytics) +- Auto-delete after expiration +- Optional: Collect results back (if onsite tech submits form) + +--- + +## Feature Priority Matrix + +| Feature | MVP | Phase 2 | Phase 3 | Phase 4 | +|---------|-----|---------|---------|---------| +| Tree Navigation | ✓ | | | | +| Basic Export | ✓ | | | | +| User Auth | ✓ | | | | +| 5 Starter Trees | ✓ | | | | +| Team Management | | ✓ | | | +| Tree Editor | | ✓ | | | +| Mobile Responsive | | ✓ | | | +| Custom Branches | | ✓ | | | +| File Attachments | | | ✓ | | +| Offline Mode | | | ✓ | | +| Advanced Export (PDF) | | | ✓ | | +| Client Context | | | ✓ | | +| Send to Engineer | | | ✓ | | +| Analytics Dashboard | | | ✓ | | +| Documentation Links | ✓ | Enhanced | | | +| API | | | | ✓ | +| Automation Integration | | | | ✓ | +| PSA Integrations | | | | ✓ | +| Marketplace | | | | Later | + +--- + +## User Stories + +### Michael (Senior Engineer) +- "I need to quickly document my troubleshooting steps for a ticket" +- "I want to reuse proven troubleshooting paths instead of reinventing" +- "I need to attach screenshots to show what I found" +- "I want to create custom trees for our specific environment" +- "I need this to work when I'm on-site with poor connectivity" + +### Junior Engineer +- "I need guidance on how to troubleshoot issues I'm not familiar with" +- "I want to know what commands to run and what results to expect" +- "I need links to documentation when I get stuck" +- "I want to learn from what senior engineers do" + +### Onsite Technician +- "I need simple, step-by-step instructions I can follow" +- "I want a checklist I can print and bring to the server room" +- "I need to report back results to the remote engineer" + +### MSP Manager +- "I need to see what my team is working on" +- "I want to ensure we're following consistent procedures" +- "I need metrics on troubleshooting time and success rates" +- "I want to identify training gaps based on common issues" diff --git a/05-QUESTIONS-AND-ACTION-ITEMS.md b/05-QUESTIONS-AND-ACTION-ITEMS.md new file mode 100644 index 00000000..0fc4346c --- /dev/null +++ b/05-QUESTIONS-AND-ACTION-ITEMS.md @@ -0,0 +1,342 @@ +# Questions & Action Items + +## Questions for Michael + +### Timeline & Resources + +**1. Timeline Pressure** +- Do you need something usable in 2 weeks, or can we take 4-6 weeks to build something more polished? +- What's driving the timeline? (Personal productivity? Team need? Demonstration?) +> 50% personal and 50% I see a need in the market + +**2. Your Involvement** +- Can you commit ~2 hours per week for feedback/testing during development? +- Or do you need this to be more hands-off until it's ready? +- Preferred communication: Slack/email/scheduled calls? +> I can commit likely more than 2 hours a week. I'm willing to spend most of my evenings working on this if needed. + +**3. Hosting Budget** +- Start with free tier on Render/Railway (with limitations)? +- Willing to spend $10-20/month for better performance/storage? +- Company paying or personal expense? +> I'm definitely willing to spend $20/month +**4. Team Size** +- How many engineers would be using this in the first 3 months? + - 1-3 people? (affects database/hosting decisions) + - 5-10 people? + - 10+ people? +- Are they all at the same skill level as you, or mix of junior/senior? +> In the first 3 months, I could see maybe 3 people other than myself at maximum. Depending upon how quickly a working model is out, it could potentially be more based upon adoption rate and how easy it is to build out the trees. + +**5. Branding** +- Personal project with your name on it? ("ChihlasTree" or similar) +- MSP company tool? (Company branding) +- Intentionally generic for broader appeal? (TroubleTree, DiagPath, etc.) +> This started as a personal project idea, however, making money from this idea is the goal. So generic may not be bad. +--- + +## Action Items for Michael + +### High Priority (Before Development Starts) + +**1. Document Your Top 5 Troubleshooting Scenarios** + +For each scenario, I need: +- Issue name/category +- First thing you check +- What the possible outcomes are +- What you do for each outcome +- How you know when you've solved it +- Common pitfalls or edge cases + +**Example Template:** +``` +Issue: FSLogix Profile Not Loading +Category: Citrix/Virtual Desktop + +Step 1: Can user log into server? +├─ YES → Step 2: Check FSLogix service +└─ NO → Different tree (AD/Licensing issue) + +Step 2: Is FSLogix service running? +├─ RUNNING → Step 3: Check frxtray.exe in task manager +├─ STOPPED → Step 4: Start service, check event log +└─ STUCK → Step 5: Kill and restart, check file locks + +Step 3: Does user have profile VHD in share? +├─ YES → Step 6: Check permissions on VHD +└─ NO → Step 7: Check FSLogix registry settings + +... (continue until resolution) + +Common Pitfalls: +- VHD file locked by another server +- Profile path in registry has typo +- Antivirus blocking VHD access + +Resolution Indicators: +- User can log in successfully +- Profile loads within 30 seconds +- No FSLogix errors in Event Viewer +``` +> For the above section, please see a document called TS-EXAMPLES.md in this project folder. + +**Your Top 5 to Document:** +1. [ ] Citrix VDA Not Registering +2. [ ] FSLogix Profile Issues +3. [ ] Active Directory Replication Failure +4. [ ] SonicWall VPN Tunnel Down +5. [ ] User Unable to Access File Share (permissions vs. connectivity) + +**2. Export Format Sample** + +Create a sample ticket note from a recent ticket that shows: +- How you currently document troubleshooting steps +> Typically this is done with bullet points with short concise sentences. At times there will be screenshots and if I think a specific command that is run is important, I will put that in too. +- The level of detail your ticketing system needs +> As detailed as possible without being bloated +- Any specific formatting requirements +> Nothing specific, I'm open to what works best +- Whether you include timestamps, commands run, etc. +> Timestamps are sometimes included, commands are also included unless it becomes an entire script in which case that is added as an attachment. + +This will help me design exports that match your existing workflow. + +**3. Team Feedback (Optional but Valuable)** + +If possible, ask 1-2 colleagues: +- "If there was a tool that guided you through troubleshooting and automatically wrote your ticket notes, what would make it actually useful vs. just another tool to learn?" +- What's their biggest pain point in documenting work? +- Do they follow consistent troubleshooting paths, or is it different every time? + +--- + +### Medium Priority (Can Be Done During Development) + +**4. Documentation Link Collection** + +Start collecting useful documentation links you reference frequently: +- Microsoft Learn articles +- Citrix documentation +- Internal wiki pages +- YouTube tutorials +- Blog posts that have saved you + +We'll integrate these into the trees as we build them. + +**5. Client Context Template** + +For 1-2 of your main clients (like City of Warner Robins), document: +- Domain controller names +- Key server hostnames +- Network information (subnets, VPN gateway) +- Common issues specific to that client +- Primary contacts + +This will help us design the client context feature properly. + +**6. Script Inventory** + +List your PowerShell scripts/tools that you'd eventually want to integrate: +- ChihlasChecker scripts +- Service restart scripts +- Common diagnostic commands +- Any automation you do repeatedly + +We won't integrate these in MVP, but good to know what exists for Phase 4. + +--- + +### Low Priority (Nice to Have) + +**7. Naming Ideas** + +Brainstorm some names for the tool: +- Should it have "Chihlas" in the name? (personal branding) +- Should it sound MSP-specific or generic? +- Should it be playful or professional? + +Some ideas to react to: +- TroubleTree +- DecisionPath +- FlowDoc +- DiagnosticFlow +- PathFinder +- TicketFlow +- ChihlasTree +- MSP Navigator + +**8. Long-term Vision** + +Think about: +- Would you want to eventually sell this to other MSPs? +- Keep it as internal tool for your company? +- Open-source it to build reputation? +- Use it as consulting/training material? +> I would like to profit off this to other MSPs, internal IT teams, and I can see applications for this in many other markets outside of IT. This could end up being used in call centers, or telemarketing even. This could also be used on a consulting basis for other MSPs and private clients. + + +This affects architectural decisions (multi-tenancy, white-labeling, etc.) + +--- + +## Decision Log + +*This section will be updated as we make key decisions* + +| Date | Decision | Rationale | +|------|----------|-----------| +| TBD | Tech stack: React + FastAPI + PostgreSQL | Michael learning Python, modern stack, flexible | +| TBD | Hosting: [To be decided] | Based on budget and team size | +| TBD | Name: [To be decided] | Based on branding goals | +| TBD | Timeline: [To be decided] | Based on urgency and availability | + +--- + +## Next Steps + +### Immediate (This Week) +1. [ ] Michael answers the 5 key questions above +2. [ ] Michael documents 2-3 troubleshooting scenarios in detail +3. [ ] Michael provides sample export format +4. [ ] Decision on timeline and hosting budget +5. [ ] Confirm tech stack choices + +### Week 1 (When Development Starts) +1. [ ] Set up development environment +2. [ ] Create project repository (GitHub) +3. [ ] Initial database schema +4. [ ] Basic authentication working +5. [ ] First tree manually created in database + +### Week 2 +1. [ ] Basic tree navigation UI functional +2. [ ] Michael tests with real scenario +3. [ ] Iterate based on feedback +4. [ ] Add export functionality +5. [ ] Create 2-3 more trees + +### Week 3 +1. [ ] Polish UI based on usage +2. [ ] Fix critical bugs +3. [ ] Deploy to production (staging environment) +4. [ ] Michael uses on real tickets +5. [ ] Collect usage data and feedback + +--- + +## Open Questions & Parking Lot + +*Questions that arise during development but don't need immediate answers* + +### Technical Questions +- Should we support tree branching (go to Tree B from Tree A)? +- How deep should tree history go? (unlimited or limited?) +- Should users be able to fork/duplicate trees? +- Real-time collaboration on trees? (Google Docs style) + +### Product Questions +- Gamification? (badges for completing troubleshooting, streaks?) +- AI suggestions based on past sessions? +- Integration with time tracking tools? +- Mobile app vs. responsive web? + +### Business Questions +- Pricing model if commercialized? +- Target market research needed? +- Patent/trademark considerations? +- Partnership opportunities with PSA vendors? + +--- + +## Meeting Notes Template + +*For regular check-ins during development* + +### Meeting [Date] + +**Attendees:** + +**Agenda:** +1. Review progress since last meeting +2. Demo new features +3. Gather feedback +4. Discuss blockers +5. Plan next sprint + +**Progress:** +- [ ] Item 1 +- [ ] Item 2 + +**Feedback:** +- + +**Blockers:** +- + +**Next Sprint Goals:** +1. +2. +3. + +**Action Items:** +- [ ] Michael: [task] +- [ ] Development: [task] + +**Next Meeting:** [Date/Time] + +--- + +## Resources & References + +### Documentation +- React: https://react.dev/ +- FastAPI: https://fastapi.tiangolo.com/ +- PostgreSQL: https://www.postgresql.org/docs/ +- Tailwind CSS: https://tailwindcss.com/docs + +### Design Inspiration +- Linear (project management): https://linear.app/ +- Notion (documentation): https://notion.so/ +- Flowchart tools: Lucidchart, Miro, Whimsical + +### Similar Tools (Competitors/Inspiration) +- ServiceNow Knowledge Base +- IT Glue +- Atlassian Confluence Decision Trees +- Custom runbook platforms + +### Learning Resources +- "Don't Make Me Think" (UX book) +- "The Mom Test" (customer development) +- MSP Reddit communities +- Spiceworks forums + +--- + +## Contact & Communication + +**Primary Communication Channel:** [To be decided] +- Slack? +- Email? +- Discord? +- GitHub Issues? + +**Preferred Meeting Schedule:** [To be decided] +- Weekly check-ins? +- Bi-weekly reviews? +- Ad-hoc as needed? + +**Response Time Expectations:** +- Urgent issues (blocking development): [X hours] +- Feature feedback: [X days] +- General questions: [X days] + +--- + +## Version History + +| Version | Date | Changes | Author | +|---------|------|---------|--------| +| 0.1 | 2026-01-22 | Initial draft | Claude | +| | | | | diff --git a/MICHAEL-NOTES.md b/MICHAEL-NOTES.md new file mode 100644 index 00000000..a3ec18f7 --- /dev/null +++ b/MICHAEL-NOTES.md @@ -0,0 +1,86 @@ +# Notes/Thoughts/Ideas + +This file is ignored by Claude (see .clauignore). +Use this for personal thoughts, todos, and reminders. + +## 📝 Quick Thoughts + +- +- +- + +## ✅ Personal TODO + +- [ ] Answer the 5 key questions in docs/05-QUESTIONS-AND-ACTION-ITEMS.md +- [ ] Decide on app name (TroubleTree? DecisionPath? MSP Navigator?) +- [ ] Set up Render account for free tier testing +- [ ] + +## 💡 Feature Ideas (Future) + +- Voice-guided troubleshooting for hands-free operation +- Integration with Teams/Slack for notifications +- AI suggestions based on past sessions +- Mobile app for on-site work + +## 🐛 Bugs to Track + +- + +## 🎯 This Week's Focus + +**Goal:** + +**Tasks:** +- +- +- + +## 💬 Questions for the Team + +- +- + +## 📊 Metrics to Watch + +- How many tickets am I using this on? +- Time saved per ticket? +- Are exports actually useful? + +## 🔧 Technical Debt + +- + +## 🎨 Design Ideas + +**1. Custom note formatting** +- Add a feature that allows the formatting of the ticket notes when they've been finished. For instance, if we have a 'send notes as email' feature, it would be a good idea to allow the customization of the ticket notes to include the ticket number, the client, the engineer who worked on it, and any other fields that are chosen to be added to the notes template. + +**2. Options on the go** +- I think another valuable idea would be to allow users to select a troubleshooting step while in the midst of their tree. For instance, if they're in the middle of troubleshooting and they decide to try something outside of the current available options, allow them to select a pre-made troubleshooting technique and then take them to a point in the tree that makes sense. If more context is needed, I can provide it when we decide to work on this. +- + +## 💰 Budget Tracking + +**Current Hosting Cost:** $0 (Render free tier) +**Expected Cost in Production:** ~$25-35/month (Railway Pro) + +## 📅 Timeline Tracking + +**Week 1:** Project planning ✅ +**Week 2:** Backend development +**Week 3:** Frontend + testing +**Week 4:** Deploy and dogfood + +## 🎓 Things I'm Learning + +- +- + +## 🔗 Useful Links + +- Render Dashboard: https://dashboard.render.com +- Railway Dashboard: https://railway.app +- Project Repo: [add when created] + +--- diff --git a/QUICK-START.md b/QUICK-START.md new file mode 100644 index 00000000..5bdf3e90 --- /dev/null +++ b/QUICK-START.md @@ -0,0 +1,167 @@ +# Quick Start Guide + +## Opening This Project in VS Code + +### Option 1: From Terminal +```bash +cd /home/claude/troubleshooting-tree-app +code . +``` + +### Option 2: From VS Code +1. Open VS Code +2. File → Open Folder +3. Navigate to `/home/claude/troubleshooting-tree-app` +4. Click "Open" + +--- + +## What's in This Project + +``` +troubleshooting-tree-app/ +│ +├── README.md # Main project overview +│ +└── docs/ + ├── 01-PROJECT-OVERVIEW.md # Vision, goals, success criteria + ├── 02-TECHNICAL-ARCHITECTURE.md # System design, tech stack, database schema + ├── 03-DEVELOPMENT-ROADMAP.md # Phases, timeline, milestones, risks + ├── 04-FEATURE-SPECIFICATIONS.md # Detailed feature descriptions + └── 05-QUESTIONS-AND-ACTION-ITEMS.md # What Michael needs to answer/do next +``` + +--- + +## Recommended Reading Order + +### First Time Through (30-45 minutes) +1. **README.md** (10 min) - Get the big picture +2. **01-PROJECT-OVERVIEW.md** (5 min) - Understand the vision +3. **04-FEATURE-SPECIFICATIONS.md** (15 min) - See what we're building +4. **05-QUESTIONS-AND-ACTION-ITEMS.md** (10 min) - Know what you need to do next + +### Deep Dive (1-2 hours) +5. **02-TECHNICAL-ARCHITECTURE.md** (30 min) - How it will be built +6. **03-DEVELOPMENT-ROADMAP.md** (30 min) - When things will happen + +--- + +## Next Steps for Michael + +### Immediate (This Week) + +1. **Answer the 5 Key Questions** in `05-QUESTIONS-AND-ACTION-ITEMS.md`: + - Timeline needs (2 weeks or 4-6 weeks?) + - Time commitment (2 hours/week for feedback?) + - Hosting budget ($0, $10-20, or more?) + - Team size (how many users?) + - Branding (personal, company, or generic?) + +2. **Document 2-3 Troubleshooting Scenarios** (Start with these): + - Citrix VDA Not Registering + - FSLogix Profile Issues + - Active Directory Replication Failure + + Use the template in `05-QUESTIONS-AND-ACTION-ITEMS.md` + +3. **Provide Sample Ticket Notes** + - Copy/paste an example of how you currently document troubleshooting + - This helps design export formats that match your workflow + +4. **Review All Docs** + - Read through everything + - Add comments/questions/ideas directly in the markdown files + - Flag anything unclear or that you disagree with + +### This Weekend (Optional) + +- Start collecting documentation links you reference frequently +- Think about client context for Warner Robins (server names, etc.) +- List your PowerShell scripts that might integrate later +- Brainstorm names for the tool + +--- + +## Tips for Reviewing in VS Code + +### Useful Extensions +- **Markdown All in One** - Preview, formatting, shortcuts +- **Markdown Preview Enhanced** - Better preview with more features +- **GitLens** (if we add version control) - Track changes + +### Keyboard Shortcuts +- `Ctrl+Shift+V` - Open markdown preview +- `Ctrl+K V` - Open preview side-by-side +- `Ctrl+F` - Find in file +- `Ctrl+Shift+F` - Find in all files + +### Making Notes +You can either: +1. Add comments directly in the markdown (I can read them) +2. Create a new file: `MICHAEL-NOTES.md` with your thoughts +3. Use VS Code's comment feature (highlight text, right-click) + +--- + +## Questions While Reading? + +Just add them anywhere in the docs like this: + +```markdown +> **MICHAEL'S QUESTION:** What if the tree has more than 20 steps? +> Won't that be too long? +``` + +Or create a `QUESTIONS.md` file with all your questions in one place. + +--- + +## What This Project Could Become + +**In 3 months:** +- You're using it daily for 50%+ of your tickets +- Saving 30+ minutes per complex ticket +- Documentation is consistently professional +- Junior engineers learning faster + +**In 6 months:** +- Entire team using it +- 50+ trees covering most common issues +- Integrations with your PSA +- Automation running directly from trees + +**In 1 year:** +- Other MSPs want to use it +- Potential revenue stream +- Industry recognition +- Speaking opportunities at MSP conferences + +**In 2 years:** +- Established product in MSP space +- Community marketplace of trees +- AI-powered suggestions +- Full automation platform + +--- + +## Remember + +This is YOUR tool. Everything in these documents is a proposal based on our conversation. If something doesn't feel right: + +- Question it +- Change it +- Suggest alternatives +- Tell me why it won't work + +The goal is to build something that actually solves your problem, not to follow a perfect plan that doesn't work in reality. + +--- + +## Contact + +Questions? Thoughts? Ideas? + +Just respond in chat or create a `FEEDBACK.md` file with your thoughts. + +Let's build something special! 🚀 diff --git a/README.md b/README.md new file mode 100644 index 00000000..e5925a64 --- /dev/null +++ b/README.md @@ -0,0 +1,353 @@ +# Troubleshooting Decision Tree Application + +> Transform chaos into clarity - guided troubleshooting with automatic documentation for MSP engineers. + +## Project Status: 📋 Planning Phase + +Currently in the planning and architecture phase. Development will begin once key decisions are made and initial troubleshooting scenarios are documented. + +--- + +## The Problem + +MSP engineers face constant context switching between diverse technical issues (file shares, server outages, VPN failures, Active Directory problems). This creates: + +- **Cognitive overload:** 15-25 minutes to regain focus after each context switch +- **Inconsistent documentation:** Under pressure, notes are rushed or incomplete +- **Lost tribal knowledge:** Best troubleshooting paths live only in senior engineers' heads +- **Repeated work:** Same issues investigated from scratch each time +- **Burnout:** Research shows context switching is a major contributor to burnout + +## The Solution + +An intelligent decision tree system that: + +✅ **Guides** engineers through proven troubleshooting paths +✅ **Captures** decisions and notes automatically as you work +✅ **Generates** professional ticket documentation with one click +✅ **Builds** institutional knowledge that improves over time +✅ **Reduces** cognitive load during high-stress situations + +### Success Metric +If Michael (our primary user) uses this tool for **50% of his tickets in 3 months**, we've succeeded. + +--- + +## Key Features + +### MVP (Weeks 1-3) +- 🌳 **Tree Navigation** - Step-by-step guided troubleshooting +- 📝 **Automatic Notes** - Capture context at each decision point +- 📄 **Export** - Generate professional documentation (plain text, markdown, HTML) +- 🔐 **Multi-User** - Team authentication and access control +- 📚 **Documentation Links** - Contextual links to KB articles and vendor docs + +### Phase 2 (Weeks 4-6) +- 👥 **Team Management** - Controlled authorship, shared access +- ✏️ **Tree Editor** - Visual interface to create/modify decision trees +- 📱 **Mobile Responsive** - Works on phone/tablet for on-site work +- 🔀 **Custom Branches** - Add unique steps on-the-fly during troubleshooting +- 🔍 **Search & Categories** - Find the right tree quickly + +### Phase 3 (Weeks 7-12) +- 📎 **Attachments** - Upload screenshots, logs, command outputs +- 💾 **Offline Mode** - Continue working without internet, sync when back online +- 🏢 **Client Context** - Auto-fill client-specific details (server names, topologies) +- 📧 **Send to Engineer** - Generate simplified checklist for onsite techs +- 📊 **Analytics** - Track usage, common paths, team performance + +### Phase 4 (Months 4-6) +- 🔌 **API & Integrations** - Connect to ConnectWise, Kaseya, LabTech +- ⚡ **Automation** - Execute PowerShell scripts directly from trees +- 🏢 **Enterprise Features** - SSO, white-labeling, advanced RBAC +- 🌐 **Marketplace** - Share and discover community-contributed trees + +--- + +## Tech Stack + +### Frontend +- **React** - Modern, flexible, excellent offline support +- **Tailwind CSS** - Rapid UI development +- **Service Workers** - Offline capability +- **IndexedDB** - Local data storage + +### Backend +- **Python FastAPI** - Modern, fast, async support +- **SQLAlchemy** - ORM with async support +- **PostgreSQL** - Reliable database with excellent JSON support +- **Alembic** - Database migrations + +### Infrastructure +- **S3-Compatible Storage** - File attachments (MinIO for dev, S3/Spaces for prod) +- **Railway/Render** - Simple hosting to start +- **Docker** - Containerized development environment + +--- + +## Project Structure + +``` +troubleshooting-tree-app/ +├── docs/ +│ ├── 01-PROJECT-OVERVIEW.md # Vision, goals, market analysis +│ ├── 02-TECHNICAL-ARCHITECTURE.md # System design, data models, API specs +│ ├── 03-DEVELOPMENT-ROADMAP.md # Phases, timeline, milestones +│ ├── 04-FEATURE-SPECIFICATIONS.md # Detailed feature descriptions +│ └── 05-QUESTIONS-AND-ACTION-ITEMS.md # Decisions needed, next steps +├── backend/ # Python FastAPI application (future) +├── frontend/ # React application (future) +├── database/ # Database schemas, migrations (future) +└── README.md # This file +``` + +--- + +## Getting Started + +### For Michael (Primary User) + +**Immediate Action Items:** + +1. **Answer Key Questions** (see `docs/05-QUESTIONS-AND-ACTION-ITEMS.md`) + - Timeline needs + - Budget for hosting + - Team size + - Branding preferences + +2. **Document 5 Troubleshooting Scenarios** + - Citrix VDA Not Registering + - FSLogix Profile Issues + - Active Directory Replication Failure + - SonicWall VPN Tunnel Down + - User Unable to Access File Share + + See template in `05-QUESTIONS-AND-ACTION-ITEMS.md` + +3. **Provide Sample Export** + - Show how you currently write ticket notes + - What format/level of detail is needed + +4. **Review Documentation** + - Read through all docs in `docs/` folder + - Flag anything unclear or that you disagree with + - Add your own thoughts/ideas + +### For Developers (Future) + +Once development starts: + +1. Clone repository +2. Set up development environment (Docker) +3. Install dependencies +4. Run migrations +5. Start development servers +6. See `CONTRIBUTING.md` for coding standards + +--- + +## Development Principles + +1. **User First** - Every feature must solve a real problem for Michael and his team +2. **Speed Matters** - Tool must be faster than doing it manually +3. **Progressive Enhancement** - Start simple, add complexity only when needed +4. **Offline Capable** - Many MSP sites have poor connectivity +5. **Automation-Ready** - Architecture supports future integration with scripts/tools +6. **Documentation Over Memory** - Capture tribal knowledge explicitly +7. **Fail Gracefully** - Never lose user's work, even if server fails + +--- + +## Use Cases + +### Scenario 1: Standard Troubleshooting +Michael gets a ticket: "User can't access file share" +1. Opens app, selects "File Share Access Issues" tree +2. Enters ticket number, client name +3. Follows decision tree, making selections and adding notes +4. Reaches resolution in 10 minutes +5. Clicks "Export", copies formatted notes into ticket +6. Done - professional documentation with zero extra effort + +### Scenario 2: Complex Multi-Step Issue +Michael troubleshooting Citrix VDA registration failure +1. Starts with "VDA Not Registering" tree +2. Discovers network issue, branches to "Network Connectivity" tree +3. Finds firewall blocking traffic, attaches screenshot of rule +4. Returns to VDA tree, continues troubleshooting +5. Automation script restarts services, captures output +6. VDA registers successfully +7. Exports comprehensive notes showing entire diagnostic path + +### Scenario 3: Junior Engineer Learning +New engineer Sarah gets escalated Active Directory issue +1. Selects "AD Replication Failure" tree (created by Michael) +2. Tree guides her step-by-step with commands to run +3. At each step, links to Microsoft Learn docs explain concepts +4. She adds detailed notes about what she found +5. Reaches point requiring senior help, shares session link with Michael +6. Michael reviews her work, sees exactly what she tried +7. Guides her through final steps over Slack +8. Sarah learns the process, documents it properly + +### Scenario 4: On-Site Technician +Michael needs hands at a remote site +1. Creates troubleshooting plan in app +2. Clicks "Send to Engineer", generates simplified checklist +3. Sends link to on-site tech via text +4. Tech follows steps, checks boxes, adds photos of error messages +5. Reports back results in real-time +6. Michael adjusts plan remotely if needed +7. Issue resolved with minimal back-and-forth + +--- + +## Why This Could Be Special + +### For Individual Engineers +- Save 30+ minutes per complex ticket +- Never lose track of troubleshooting progress +- Professional documentation every time +- Learn from experienced engineers' approaches +- Build personal knowledge base over time + +### For MSP Teams +- Standardize troubleshooting procedures +- Onboard junior engineers faster +- Capture institutional knowledge before engineers leave +- Improve ticket documentation quality +- Identify training gaps and common issues +- Track team performance and efficiency + +### For the Market +- 30,000+ MSPs in North America alone +- Adjacent markets: Internal IT, DevOps, Technical Support +- Current solutions are either too generic (flowchart tools) or too rigid (static runbooks) +- **Unique Value:** Purpose-built for technical troubleshooting with automation integration + +### Potential Business Model +- **Free Tier:** Personal use, limited trees +- **Pro Tier:** $15-25/user/month - Team features, unlimited trees, analytics +- **Enterprise:** Custom pricing - API, SSO, white-labeling +- **Marketplace:** Revenue share on community trees +- **Professional Services:** Custom tree development, training, consulting + +--- + +## Inspiration & Similar Tools + +### What Exists Today +- **ServiceNow Knowledge Base** - Good for static docs, not interactive troubleshooting +- **IT Glue** - Documentation repository, not a troubleshooting guide +- **Confluence Decision Trees** - Generic flowcharts, not execution-focused +- **Custom Runbooks** - Static, not adaptive, no automation + +### What We're Building +Imagine if ServiceNow Knowledge, Flowchart tools, and PowerShell automation had a baby specifically designed for MSP troubleshooting. That's this. + +--- + +## FAQ + +**Q: Why not just use a wiki or documentation system?** +A: Wikis are great for reference, but they don't guide you through troubleshooting in real-time or automatically generate ticket notes from your actions. + +**Q: Won't creating trees take more time than just doing the work?** +A: Initially, yes. But after 2-3 uses of a tree, you've saved more time than you spent creating it. Plus, the tree captures knowledge that helps the entire team. + +**Q: What if the tree doesn't cover my specific issue?** +A: You can add custom branches on-the-fly during troubleshooting. These custom paths can then be incorporated into the tree for next time. + +**Q: How is this different from a flowchart tool?** +A: Flowcharts are static diagrams. This is an active troubleshooting companion that captures your work and generates documentation. + +**Q: Can I use this offline?** +A: Yes (Phase 3). Trees are cached locally, you can work offline, and changes sync when you're back online. + +**Q: Will this replace my ticketing system?** +A: No, it complements it. You still create tickets in your PSA, but this generates the detailed notes you paste into tickets. + +**Q: Can I automate steps?** +A: Yes (Phase 4). Integrate PowerShell scripts and other automation that can be triggered directly from decision nodes. + +--- + +## Contributing + +This is currently a private project in planning phase. Once we move to active development, we'll create a `CONTRIBUTING.md` with: +- Code of conduct +- Development workflow +- Coding standards +- Testing requirements +- PR process + +--- + +## Contact & Feedback + +**Primary User:** Michael Chihlas +**Project Lead:** [To be determined] +**Communication:** [To be determined] + +For questions, suggestions, or to get involved, contact Michael. + +--- + +## License + +[To be determined] + +Options being considered: +- Open source (MIT/Apache 2.0) - maximize adoption +- Source-available with commercial license - protect business interests +- Proprietary - if building as commercial product + +--- + +## Acknowledgments + +- Research on context switching and burnout that inspired this project +- MSP community for sharing their pain points and workflows +- All the engineers who've struggled with documentation and wished for a better way + +--- + +## Roadmap at a Glance + +``` +└─ [📋 Planning] ← WE ARE HERE + ├─ 📝 Document requirements + ├─ ✅ Make key decisions + └─ 🏗️ Setup initial architecture + +└─ [🚀 Week 1-3: MVP] + ├─ Basic tree navigation + ├─ Export functionality + └─ 5 starter trees + +└─ [👥 Week 4-6: Team Ready] + ├─ Team management + ├─ Tree editor + └─ Mobile responsive + +└─ [💼 Week 7-12: Professional] + ├─ Attachments + ├─ Offline mode + └─ Analytics + +└─ [🔌 Month 4-6: Platform] + ├─ API & integrations + ├─ Automation + └─ Enterprise features + +└─ [🚀 Beyond: Growth] + ├─ Marketplace + ├─ AI features + └─ Mobile apps +``` + +--- + +**Last Updated:** 2026-01-22 +**Project Status:** Planning Phase +**Next Milestone:** Answer key questions, document first 3 troubleshooting scenarios diff --git a/TS-EXAMPLES.md b/TS-EXAMPLES.md new file mode 100644 index 00000000..6472a859 --- /dev/null +++ b/TS-EXAMPLES.md @@ -0,0 +1,783 @@ +# Troubleshooting Scenarios for Decision Tree App + +## Scenario 1: FSLogix Profile Not Loading + +### Issue Details +**Issue Name:** FSLogix Profile Not Loading +**Category:** Citrix/Virtual Desktop +**Estimated Time:** 10-15 minutes +**Common For:** Warner Robins City, other Citrix environments + +### First Thing You Check +Can the user log into the server at all? + +### Decision Tree + +**Step 1: Can user log into server?** +- **YES** → Step 2: Check FSLogix service status +- **NO** → Different tree (AD account/licensing issue) + +**Step 2: Is FSLogix service running on the server?** +- **RUNNING** → Step 3: Check frxtray.exe in user's task manager +- **STOPPED** → Step 4: Start service and check event log for cause +- **STUCK (Starting/Stopping)** → Step 5: Kill service process and restart + +**Step 3: Is frxtray.exe running in user's task manager?** +- **YES** → Step 6: Check if profile VHD exists in share +- **NO** → Step 7: Check FSLogix agent installation +- **MULTIPLE INSTANCES** → Step 8: Kill all frxtray.exe, log user off, try again + +**Step 4: Service Start Result** +*Action: Start-Service -Name 'frxsvc'* +- **Started successfully** → Step 9: Check Event Viewer for previous failure reason +- **Failed to start** → Step 10: Check service dependencies (NetLogon, RPC) +- **Started but stopped again** → Step 11: Check for file locks or permissions + +**Step 5: Service Kill and Restart** +*Action: Stop-Process -Name frxsvc -Force; Start-Service frxsvc* +- **Service now running** → Step 3: Verify frxtray.exe +- **Still stuck** → Step 12: Check for corrupt profile or registry + +**Step 6: Does user have a profile VHD in the share?** +*Check: \\server\fslogix\username\Profile_username.vhdx* +- **YES, file exists** → Step 13: Check VHD file permissions +- **NO, file missing** → Step 14: Check FSLogix registry path configuration +- **YES, but 0 bytes** → Step 15: Delete corrupt VHD, recreate profile + +**Step 7: Is FSLogix agent installed?** +*Check: C:\Program Files\FSLogix\Apps\frxsvc.exe exists* +- **YES** → Step 16: Repair FSLogix agent +- **NO** → Step 17: Install FSLogix agent + +**Step 8: Multiple frxtray instances** +*Action: Get-Process frxtray | Stop-Process -Force* +- **Killed successfully** → Log user off, have them log back in +- **Cannot kill** → Step 18: Check for file/folder locks + +**Step 9: Check Event Viewer** +*Action: Check Application log for FSLogix errors* +- **Error 50 (Can't access network path)** → Step 19: Verify network path accessible +- **Error 13 (VHD locked)** → Step 20: Check for locks on VHD from other servers +- **Error 52 (Profile path not found)** → Step 14: Check registry settings + +**Step 10: Check Service Dependencies** +*Action: Get-Service NetLogon, RpcSs status* +- **All running** → Step 21: Check antivirus blocking +- **NetLogon stopped** → Start NetLogon, then retry FSLogix +- **RPC stopped** → Critical issue, escalate to senior engineer + +**Step 11: Check for File Locks** +*Action: Run Chihlas file lock checker on profile share* +- **No locks** → Step 22: Check disk space on profile server +- **Locked by another server** → Step 20: Release lock or force user logoff from other session + +**Step 13: Check VHD Permissions** +*Action: Get-Acl on Profile_username.vhdx* +- **User has Full Control** → Step 23: Try mounting VHD manually +- **User missing permissions** → Step 24: Grant user full control +- **Everyone has permission but still fails** → Step 25: Check parent folder permissions + +**Step 14: Check FSLogix Registry Path** +*Check: HKLM\SOFTWARE\FSLogix\Profiles - VHDLocations* +- **Path is correct** → Step 26: Check DNS resolution of server name +- **Path has typo** → Fix registry path, log user off and back on +- **Path uses old server** → Update to correct server path + +**Step 15: Delete Corrupt VHD** +*Action: Delete 0-byte VHD file* +- **Deleted successfully** → User will get new profile on next login +- **Cannot delete (in use)** → Step 20: Check locks, force release + +**Step 17: Install FSLogix Agent** +*Action: Run FSLogix installer from network share* +- **Installed successfully** → Reboot server, have user try again +- **Installation failed** → Step 27: Check server OS version compatibility + +**Step 19: Verify Network Path** +*Action: Test-Path \\server\fslogix from problem server* +- **Accessible** → Step 28: Check firewall between servers +- **Not accessible** → Check DNS, check network connectivity +- **Accessible but slow** → Step 29: Check network performance + +**Step 20: Check VHD Locks** +*Action: Use openfiles /query or handle.exe to check locks* +- **Locked by same server** → Kill locking process +- **Locked by different server** → Log user off from that server +- **Lock from crashed session** → Clear stale session, release lock + +**Step 21: Check Antivirus** +*Action: Check if AV is scanning/blocking FSLogix folders* +- **FSLogix folders excluded** → Step 30: Check Windows Defender exclusions too +- **Not excluded** → Add exclusions, restart FSLogix service +- **Exclusions present but still blocking** → Temporarily disable AV to test + +**Step 23: Try Mounting VHD Manually** +*Action: Mount-VHD -Path \\server\fslogix\...\Profile.vhdx* +- **Mounts successfully** → Profile is good, issue elsewhere (back to Step 2) +- **Fails to mount** → Step 31: Check VHD integrity +- **Mounts but takes forever** → Step 29: Network performance issue + +**Step 24: Grant User Permissions** +*Action: icacls add full control for user on VHD* +- **Permissions granted** → Have user log off and back on +- **Cannot modify permissions** → Check if admin has access, check share permissions + +**Step 31: Check VHD Integrity** +*Action: Test-VHD -Path ... in PowerShell* +- **VHD is healthy** → Issue is mounting or permissions +- **VHD is corrupt** → Step 15: Delete and recreate +- **Cannot test (access denied)** → Permission issue on share + +**RESOLUTION: Profile loads successfully** + +### Common Pitfalls +- VHD file locked by another server (user has session on multiple servers) +- Profile path in registry has typo or uses old server name +- Antivirus blocking VHD access or scanning profile folder +- NetLogon service stopped preventing network authentication +- Disk full on profile share +- DNS not resolving profile server name +- Stale sessions from crashed RDP connections + +### Resolution Indicators +- User can log in successfully +- Profile loads within 30 seconds +- No FSLogix errors in Event Viewer +- frxtray.exe running in task manager +- User's desktop, documents appear correctly + +### Documentation Links +- FSLogix Profile Troubleshooting: https://docs.microsoft.com/en-us/fslogix/troubleshooting-profile-container +- Event Log Error Codes: https://docs.microsoft.com/en-us/fslogix/profile-container-configuration-reference +- VHD Troubleshooting: Internal KB #FSL-001 + +--- + +## Scenario 2: Citrix VDA Not Registering + +### Issue Details +**Issue Name:** Citrix VDA Not Registering with Delivery Controller +**Category:** Citrix/Virtual Desktop +**Estimated Time:** 10-20 minutes +**Common For:** Warner Robins City, all Citrix environments + +### First Thing You Check +Can you ping the VDA from the Delivery Controller? + +### Decision Tree + +**Step 1: Can you ping VDA from DDC?** +*Action: Test-Connection -ComputerName VDA-HOSTNAME* +- **YES (replies)** → Step 2: Check VDA service status +- **NO (request timed out)** → Step 3: Network connectivity issue + +**Step 2: What is VDA service status?** +*Action: Get-Service -Name 'BrokerAgent' on VDA* +- **RUNNING** → Step 4: Check DDC connection from VDA +- **STOPPED** → Step 5: Start VDA service +- **STUCK** → Step 6: Force kill and restart service + +**Step 3: Network Connectivity Issue** +*Troubleshooting network layer* +- **VDA powered off** → Power on VDA, wait for boot +- **VDA on different subnet** → Step 7: Check routing/firewall +- **DNS not resolving** → Step 8: Check DNS configuration +- **Network cable unplugged** → Physical layer issue + +**Step 4: Can VDA reach DDC on port 80/443?** +*Action: Test-NetConnection -ComputerName DDC-HOSTNAME -Port 80* +- **Port 80 success** → Step 9: Check VDA registration in Studio +- **Port 80 blocked** → Step 10: Check firewall rules +- **DNS fails** → Step 8: Check DNS + +**Step 5: Start VDA Service** +*Action: Start-Service -Name 'BrokerAgent'* +- **Started successfully** → Step 11: Wait 60 seconds, check registration +- **Failed to start** → Step 12: Check Event Viewer for error +- **Started then stopped** → Step 13: Check service dependencies + +**Step 6: Force Kill VDA Service** +*Action: Stop-Process -Name BrokerAgent -Force* +- **Killed successfully** → Step 5: Start service normally +- **Cannot kill (access denied)** → Restart VDA server +- **Killed but immediately respawns** → Step 14: Check for loops + +**Step 7: Check Routing/Firewall** +*Between VDA and DDC* +- **Different VLANs** → Verify inter-VLAN routing configured +- **SonicWall between them** → Step 15: Check SonicWall rules +- **Switches involved** → Check VLAN tagging, trunk ports + +**Step 8: Check DNS Configuration** +*Action: Resolve-DnsName DDC-HOSTNAME from VDA* +- **Resolves correctly** → DNS is fine, go back to network troubleshooting +- **Does not resolve** → Step 16: Check VDA DNS server settings +- **Resolves to wrong IP** → Step 17: Check DNS A record + +**Step 9: Check VDA in Citrix Studio** +*Action: Open Studio > Machine Catalogs* +- **VDA shows "Registered"** → Issue resolved! +- **VDA shows "Unregistered"** → Step 18: Check ListOfDDCs registry +- **VDA not in catalog** → Step 19: Add VDA to catalog + +**Step 10: Check Firewall Rules** +*Between VDA and DDC* +- **Windows Firewall blocking** → Create rule to allow DDC traffic +- **Hardware firewall blocking** → Step 15: Update SonicWall rules +- **NSG rules (if Azure)** → Add allow rule for ports 80, 443, 1494, 2598 + +**Step 11: Wait and Verify Registration** +*Action: Wait 60 seconds, refresh Studio* +- **Now registered** → Resolution confirmed! +- **Still unregistered** → Step 18: Check ListOfDDCs +- **Shows error in Studio** → Step 20: Check specific error code + +**Step 12: Check Event Viewer** +*Action: Application log, filter for Citrix* +- **Error 1001 (cannot contact DDC)** → Step 4: Check connectivity +- **Error 1006 (auth failure)** → Step 21: Check machine account +- **Error 1035 (database connection failed)** → Escalate to DDC troubleshooting + +**Step 13: Check Service Dependencies** +*Action: Check dependent services* +- **NetLogon stopped** → Start NetLogon first +- **Remote Registry stopped** → Start Remote Registry +- **Windows Event Log stopped** → Critical, may need reboot + +**Step 15: Check SonicWall Rules** +*Between VDA subnet and DDC subnet* +- **No rule exists** → Create LAN→LAN allow rule for Citrix ports +- **Rule exists but wrong ports** → Add ports 80, 443, 1494, 2598 +- **Rule exists, looks correct** → Check packet capture on SonicWall + +**Step 16: Check VDA DNS Settings** +*Action: Get-DnsClientServerAddress on VDA* +- **Points to wrong DNS** → Set to correct DNS server +- **Points to correct DNS** → Step 17: Check DNS server itself +- **No DNS configured** → Configure DNS, restart VDA + +**Step 17: Check DNS A Record** +*On DNS server* +- **A record correct** → Clear DNS cache on VDA +- **A record wrong IP** → Update A record, clear cache +- **A record missing** → Create A record for DDC + +**Step 18: Check ListOfDDCs Registry** +*Action: Check HKLM\Software\Citrix\VirtualDesktopAgent - ListOfDDCs* +- **Points to correct DDC** → Step 22: Re-register VDA manually +- **Points to old/wrong DDC** → Update registry to correct DDC name +- **Registry key missing** → Run Citrix VDA installer repair + +**Step 19: Add VDA to Catalog** +*In Citrix Studio* +- **Added successfully** → VDA should register within 60 seconds +- **Cannot add (not found)** → Step 1: Network connectivity issue +- **Cannot add (duplicate)** → VDA may be in different catalog, search + +**Step 21: Check Machine Account** +*In Active Directory* +- **Account exists, enabled** → Step 23: Check computer trust relationship +- **Account disabled** → Enable account, restart VDA +- **Account missing** → Re-join VDA to domain + +**Step 22: Re-register VDA Manually** +*Action: Run "C:\Program Files\Citrix\Virtual Desktop Agent\BrokerAgent.exe" -RegisterWithDDC* +- **Registration successful** → Verify in Studio +- **Registration failed** → Check error message, return to Step 4 +- **Command not found** → VDA install corrupted, reinstall + +**Step 23: Check Computer Trust Relationship** +*Action: Test-ComputerSecureChannel on VDA* +- **Trust relationship good** → Back to Step 2 +- **Trust relationship broken** → Repair: Reset-ComputerMachinePassword +- **Repair failed** → Re-join domain + +**RESOLUTION: VDA shows as Registered in Studio** + +### Common Pitfalls +- Firewall blocking ports 80/443 between VDA and DDC +- DNS not resolving DDC hostname +- ListOfDDCs registry points to old/decommissioned DDC +- Machine account password expired or trust relationship broken +- VDA service won't stay running due to corrupt installation +- Network routing issue between VDA and DDC subnets +- VDA trying to register to wrong DDC in multi-site setup + +### Resolution Indicators +- VDA shows "Registered" in Citrix Studio +- Users can successfully launch sessions to VDA +- No Citrix errors in Event Viewer +- VDA appears in correct delivery group + +### Documentation Links +- VDA Registration: https://docs.citrix.com/en-us/citrix-virtual-apps-desktops/manage-deployment/vda-registration +- Troubleshooting: https://support.citrix.com/article/CTX136668 +- Event Log Errors: https://support.citrix.com/article/CTX127348 + +--- + +## Scenario 3: User Cannot Access File Share + +### Issue Details +**Issue Name:** User Cannot Access Network File Share +**Category:** File Services / Permissions +**Estimated Time:** 5-15 minutes +**Common For:** All clients with file servers + +### First Thing You Check +Can the user ping the file server? + +### Decision Tree + +**Step 1: Can user ping file server by name?** +*Action: ping FILE-SERVER-NAME* +- **YES (replies)** → Step 2: Can user access share path +- **NO (timeout/host unreachable)** → Step 3: Network connectivity issue +- **Unknown host** → Step 4: DNS resolution issue + +**Step 2: Can user access \\server\share in File Explorer?** +*Action: Navigate to \\SERVER\SHARE* +- **YES, opens** → Step 5: Check specific folder permissions +- **NO, access denied** → Step 6: Check share permissions +- **NO, network path not found** → Step 7: Check SMB service + +**Step 3: Network Connectivity Issue** +*Troubleshooting layer 3* +- **User on VPN** → Step 8: Check VPN tunnel status +- **User on different site** → Step 9: Check site-to-site connectivity +- **Server on different VLAN** → Check inter-VLAN routing +- **Cable unplugged** → Physical issue + +**Step 4: DNS Resolution Issue** +*Action: nslookup FILE-SERVER-NAME* +- **Resolves to correct IP** → Try accessing by IP: \\192.168.1.10\share +- **Does not resolve** → Step 10: Check DNS configuration +- **Resolves to wrong IP** → Step 11: Update DNS record + +**Step 5: Can user access specific folder?** +*Action: Open \\server\share\specific-folder* +- **YES** → Issue resolved! +- **NO, access denied** → Step 12: Check NTFS permissions on folder +- **Folder doesn't exist** → Verify correct path, check if moved + +**Step 6: Check Share Permissions** +*Action: Right-click share > Properties > Sharing > Permissions* +- **User has Read or Change** → Step 12: Check NTFS permissions +- **User not in permissions** → Step 13: Add user to share permissions +- **Everyone has Full Control** → Share perms OK, issue is NTFS + +**Step 7: Check SMB Service** +*Action: Get-Service -Name LanmanServer on file server* +- **Running** → Step 14: Check SMB signing requirements +- **Stopped** → Start service, verify user can access +- **Disabled** → Enable and start service + +**Step 8: Check VPN Tunnel** +*If user is remote* +- **VPN connected** → Step 15: Check VPN routing for file server subnet +- **VPN disconnected** → Reconnect VPN, retry +- **VPN connected but can't reach internal** → Step 16: Check split tunneling + +**Step 9: Site-to-Site Connectivity** +*Between user's site and file server site* +- **Ping works between sites** → Not a site link issue +- **Ping fails between sites** → Step 17: Check VPN tunnel between sites +- **Some services work, files don't** → Check port 445 specifically + +**Step 10: Check User's DNS Settings** +*Action: ipconfig /all on user's PC* +- **DNS points to DC** → Step 18: Check DNS server health +- **DNS points to wrong server** → Set correct DNS via DHCP or static +- **No DNS configured** → Configure DNS + +**Step 12: Check NTFS Permissions** +*Action: Right-click folder > Properties > Security* +- **User has Read & Execute** → User should have access +- **User not listed** → Step 19: Check group memberships +- **User has Deny** → Step 20: Remove explicit Deny + +**Step 13: Add User to Share Permissions** +*Action: Add user or user's group with appropriate access* +- **Added successfully** → User should now be able to access +- **Cannot add (grayed out)** → Check if Advanced Sharing is needed +- **Added but still fails** → Step 12: Check NTFS permissions + +**Step 14: Check SMB Signing** +*Action: Check SMB server/client signing requirements* +- **Client requires signing, server doesn't** → Enable signing on server +- **Mismatch in SMB versions** → Step 21: Enable SMB 2.0/3.0 +- **Settings match** → Not SMB signing issue + +**Step 15: Check VPN Routing** +*Verify file server subnet is routed through VPN* +- **Route exists** → Check firewall rules on VPN +- **Route missing** → Add route for file server subnet +- **Route exists but traffic blocked** → Step 22: Check firewall + +**Step 17: Check Site-to-Site VPN** +*Between locations* +- **Tunnel up** → Step 23: Check Phase 2 includes port 445 +- **Tunnel down** → Troubleshoot VPN (separate tree) +- **Tunnel flapping** → Check for routing loops + +**Step 18: Check DNS Server** +*On domain controller/DNS server* +- **DNS service running** → Check if A record exists for file server +- **DNS service stopped** → Start DNS service +- **High CPU/memory** → May need DNS server restart + +**Step 19: Check Group Memberships** +*Action: Check what groups user belongs to* +- **User in correct group** → Step 24: Run gpupdate to refresh token +- **User not in group** → Add user to appropriate group +- **User added recently** → User needs to log off and back on + +**Step 20: Remove Explicit Deny** +*Deny permissions override all allows* +- **Deny removed** → User should now have access +- **Deny is inherited** → Step 25: Check parent folder permissions +- **Cannot remove (grayed out)** → Disable inheritance, then remove + +**Step 21: Enable SMB 2.0/3.0** +*Action: Enable SMB versions on server* +- **Enabled successfully** → User should now connect +- **Already enabled** → Check Windows version compatibility +- **Cannot enable** → OS version too old, may need upgrade + +**Step 24: Refresh User Token** +*Action: Have user log off and back on (or run klist purge)* +- **After logoff/logon, works** → Resolution confirmed +- **Still fails after logoff** → Step 26: Check effective permissions + +**Step 26: Check Effective Permissions** +*Action: Advanced Security > Effective Access* +- **Shows user should have access** → Step 27: Check for inheritance issues +- **Shows user has no access** → Permission configuration error +- **Tool shows access but user still can't** → Clear SMB cache + +**RESOLUTION: User can access share and specific folders** + +### Common Pitfalls +- User has NTFS permissions but not share permissions (or vice versa) +- User added to group but hasn't logged off/on to refresh token +- Explicit Deny permission overriding Allow permissions +- DNS not resolving file server name +- Firewall blocking port 445 (SMB) +- DFS namespace issues (different issue, separate tree) +- Offline Files caching causing stale view + +### Resolution Indicators +- User can open \\server\share +- User can create/modify files if they should have write access +- File Explorer shows correct folders +- No "Access Denied" or "Network Path Not Found" errors + +### Documentation Links +- SMB Troubleshooting: https://docs.microsoft.com/en-us/windows-server/storage/file-server/troubleshoot/ +- File Permissions: Internal KB #NTFS-PERMS-001 +- DFS Issues: Internal KB #DFS-TROUBLESHOOT + +--- + +## Scenario 4: Active Directory Replication Failure + +### Issue Details +**Issue Name:** Active Directory Replication Not Working +**Category:** Active Directory / Infrastructure +**Estimated Time:** 15-30 minutes +**Common For:** Multi-DC environments, especially after DC issues + +### First Thing You Check +Can the DCs ping each other? + +### Decision Tree + +**Step 1: Can DCs ping each other by name?** +*Action: Test-Connection between all DCs* +- **YES, all reply** → Step 2: Check replication status +- **NO, some don't reply** → Step 3: Network connectivity issue +- **Name doesn't resolve** → Step 4: DNS issue + +**Step 2: What does replicadmin /showrepl show?** +*Action: repadmin /showrepl on each DC* +- **Last replication: recent (< 1 hour)** → Replication working +- **Last replication: old (> 3 hours)** → Step 5: Check for specific errors +- **Replication failing with error** → Step 6: Identify error code + +**Step 3: Network Connectivity Between DCs** +*Layer 3 troubleshooting* +- **Different sites** → Step 7: Check site link configuration +- **Firewall between DCs** → Step 8: Check firewall rules +- **Same site but can't reach** → Check switches, VLANs + +**Step 4: DNS Issues Between DCs** +*Action: nslookup DC-NAME from other DC* +- **Resolves correctly** → Not DNS issue, back to Step 1 +- **Doesn't resolve** → Step 9: Check DNS zone replication +- **Resolves to wrong IP** → Step 10: Update DNS A record + +**Step 5: Check for Specific Replication Errors** +*Review repadmin output* +- **"Last attempt was successful"** → False alarm, replication OK +- **Shows specific error code** → Step 6: Identify error code +- **No errors but time is old** → Step 11: Force replication + +**Step 6: Identify Replication Error Code** +*Common error codes* +- **Error 8606 (insufficient attributes)** → Step 12: Metadata cleanup needed +- **Error 8451/8452 (naming context)** → Step 13: Name server not advertising +- **Error 1722 (RPC server unavailable)** → Step 14: RPC/firewall issue +- **Error 1256 (domain trust issue)** → Step 15: Secure channel problem +- **Error 8614 (version mismatch)** → Step 16: Schema version issue + +**Step 7: Check Site Link Configuration** +*Action: Check AD Sites and Services* +- **Site link exists** → Step 17: Check site link schedule +- **No site link** → Create site link between sites +- **Link cost too high** → Adjust link cost if needed + +**Step 8: Check Firewall Rules Between DCs** +*Required ports for AD replication* +- **Ports 135, 389, 636, 3268, 49152+ open** → Not firewall issue +- **Some ports blocked** → Step 18: Open required AD ports +- **All ports open but still fails** → Back to Step 6 for errors + +**Step 9: Check DNS Zone Replication** +*Action: Check _msdcs zone on both DCs* +- **Zone present on both** → Step 19: Check SRV records +- **Zone missing on one DC** → Step 20: Force DNS zone replication +- **Zone present but not replicating** → Check DNS application partition + +**Step 11: Force Replication** +*Action: repadmin /syncall /AdeP* +- **Replication succeeded** → Check if ongoing or one-time issue +- **Still failing** → Step 6: Check specific error +- **Partially succeeded** → Identify which DCs failing + +**Step 12: Metadata Cleanup for Error 8606** +*Action: ntdsutil metadata cleanup* +- **Phantom DC found** → Remove phantom DC object +- **No phantoms** → Step 21: Check USN rollback +- **Cleanup completed** → Force replication, verify + +**Step 13: Name Server Not Advertising (8451/8452)** +*DC not advertising itself properly* +- **netlogon service stopped** → Start netlogon service +- **netlogon running** → Step 22: Re-register netlogon DNS records +- **After reregister, still fails** → Check DNS zone for SRV records + +**Step 14: RPC Server Unavailable (1722)** +*RPC connectivity issue* +- **Port 135 blocked** → Step 8: Open port 135 +- **Port open but RPC fails** → Step 23: Check RPC service status +- **RPC service running** → Check endpoint mapper + +**Step 15: Secure Channel Problem (1256)** +*Computer account trust issue* +- **Password mismatch** → Step 24: Reset computer account +- **Account locked** → Unlock computer account in AD +- **Account missing** → Serious issue, may need DC demotion/promotion + +**Step 16: Schema Version Mismatch (8614)** +*Schema versions don't match* +- **One DC has older schema** → Step 25: Update schema on older DC +- **Schema versions match** → May be false positive, check metadata + +**Step 17: Check Site Link Schedule** +*Action: Site link properties > Change Schedule* +- **Replication blocked in current time** → Wait or adjust schedule +- **Schedule allows replication** → Step 26: Check site link cost +- **Schedule set to never** → Configure proper schedule + +**Step 18: Open Required AD Ports** +*On firewall between DCs* +- **Rules added** → Test replication after 5 minutes +- **Cannot add rules** → Escalate to network team +- **Rules exist but traffic blocked** → Check for other firewalls + +**Step 19: Check SRV Records** +*Action: nslookup -type=SRV _ldap._tcp.dc._msdcs.DOMAIN* +- **Both DCs listed** → DNS is good +- **One DC missing** → Step 22: Re-register DNS +- **No DCs listed** → Critical DNS issue, Step 20 + +**Step 20: Force DNS Zone Replication** +*Action: repadmin /replicate for DNS partitions* +- **DNS replicated** → Verify SRV records now present +- **DNS replication failed** → Check for DNS-specific errors +- **Partial replication** → May need multiple attempts + +**Step 22: Re-register Netlogon DNS Records** +*Action: nltest /dsregdns on problem DC* +- **Registration succeeded** → Check DNS for new SRV records +- **Registration failed** → Check DNS service, Event Viewer +- **Succeeded but records still missing** → Manual creation needed + +**Step 23: Check RPC Service** +*Action: Get-Service RPCSS* +- **Running** → Step 27: Check RPC port range +- **Stopped** → Start RPCSS service (critical!) +- **Stuck starting** → Reboot DC (after-hours if possible) + +**Step 24: Reset Computer Account** +*Action: Reset-ComputerMachinePassword -Server PDC* +- **Reset successful** → Force replication, verify +- **Reset failed** → May need to reset from authoritative DC +- **After reset, still fails** → Deeper trust issue, may need demotion + +**Step 27: Check RPC Port Range** +*Action: Check dynamic port range* +- **Default range (49152-65535)** → Range is fine +- **Custom restricted range** → Step 28: Ensure both DCs use same range +- **No dynamic ports available** → Exhaustion issue, investigate + +**RESOLUTION: repadmin /showrepl shows recent successful replication on all DCs** + +### Common Pitfalls +- Firewall blocking high ports (49152+) needed for RPC +- DNS SRV records missing or incorrect +- Phantom domain controller objects in AD Sites and Services +- Secure channel broken between DCs +- Time skew between DCs (> 5 minutes causes Kerberos failures) +- Antivirus blocking AD replication traffic +- Incorrect site link configuration + +### Resolution Indicators +- repadmin /showrepl shows successful replication within last hour +- No replication errors in Directory Services event log +- dcdiag /test:replications passes +- Changes propagate between DCs within expected timeframe +- No Event ID 2042 (too long since last replication) + +### Documentation Links +- AD Replication: https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/troubleshoot/ +- Repadmin Guide: https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/cc770963(v=ws.11) +- Error Codes: Internal KB #AD-REPL-ERRORS + +--- + +## Scenario 5: Password Reset Request (Simple Example) + +### Issue Details +**Issue Name:** User Forgot Password - Needs Reset +**Category:** Account Management +**Estimated Time:** 2-5 minutes +**Common For:** Daily helpdesk task + +### First Thing You Check +Verify user's identity + +### Decision Tree + +**Step 1: Can you verify user's identity?** +*Check against company verification policy* +- **YES (verified via phone/email/manager)** → Step 2: Locate user account +- **NO (cannot verify)** → Deny request, inform user of verification process +- **User is contractor** → Step 3: Check if manager approval required + +**Step 2: Can you find user account in AD?** +*Action: Search Active Directory for username* +- **Account found** → Step 4: Check account status +- **Account not found** → Step 5: Check if name spelled correctly +- **Multiple accounts** → Step 6: Identify correct account + +**Step 3: Manager Approval for Contractor** +*Per company policy* +- **Manager approves** → Step 2: Proceed with reset +- **Manager denies** → Inform contractor, deny request +- **Cannot reach manager** → Escalate to IT manager + +**Step 4: Is account enabled?** +*Check account status* +- **Enabled** → Step 7: Reset password +- **Disabled** → Step 8: Check why disabled +- **Locked out** → Step 9: Unlock and reset + +**Step 5: Check Name Spelling** +*Verify with user* +- **Found with correct spelling** → Step 4: Check status +- **Still not found** → Check if account exists, may need creation +- **User doesn't have account** → Route to new user request process + +**Step 6: Identify Correct Account** +*Multiple John Smiths, etc.* +- **Identified by employee ID** → Step 4: Proceed +- **Identified by department** → Step 4: Proceed +- **Cannot identify** → Ask user for more info (start date, manager, etc.) + +**Step 7: Reset Password** +*Action: Set temporary password in AD* +- **Reset successful** → Step 10: Communicate new password to user +- **Cannot reset (permission denied)** → Escalate to higher-level admin +- **Reset but user still can't login** → Step 11: Check for other issues + +**Step 8: Account Disabled - Check Why** +*Look at account notes or ticket history* +- **Disabled for termination** → Do not enable, inform requester +- **Disabled for inactivity** → Step 12: Verify if user still employed +- **Disabled in error** → Enable account and reset password + +**Step 9: Unlock Account** +*Action: Unlock account in AD* +- **Unlocked successfully** → Step 7: Reset password +- **Unlock failed** → Wait 15 minutes (lockout duration), try again +- **Immediately locks again** → Step 13: Check for automated login attempts + +**Step 10: Communicate New Password** +*Securely provide temp password* +- **Told user over phone** → Instruct user must change at login +- **Sent via secure portal** → Provide portal link +- **User received password** → Step 14: Verify user can login + +**Step 11: Reset Success But Login Failed** +*After reset, user still can't login* +- **Wrong username** → Provide correct username +- **Caps Lock on** → Inform user +- **Password not synced yet** → Wait 2-3 minutes, retry +- **MFA issue** → Different troubleshooting path + +**Step 12: Verify User Still Employed** +*Check with HR or manager* +- **Still employed** → Enable account, reset password +- **Terminated** → Do not enable, close ticket +- **Unknown status** → Escalate to IT manager + +**Step 13: Check for Automated Login Attempts** +*Saved credentials somewhere* +- **Old laptop auto-logging** → Have user change password on laptop +- **Mobile device** → Remove saved password on phone +- **Service account** → Update service account password +- **Can't identify source** → Change password multiple times + +**Step 14: Verify User Can Login** +*Confirm with user* +- **Login successful** → Step 15: Set user must change password +- **Still cannot login** → Return to Step 11 +- **Login works but can't access email** → Different issue + +**Step 15: Force Password Change at Next Login** +*If not already set* +- **User will be prompted** → Document ticket, close +- **User successfully changed** → Resolution confirmed! +- **User locked out again** → May be complexity requirement issue + +**RESOLUTION: User successfully logged in with new password** + +### Common Pitfalls +- Not verifying user identity properly +- Forgetting to check if account is locked (not just disabled) +- Not telling user to change password at next login +- Multiple accounts for same name, resetting wrong one +- Account syncs slowly to other systems (email, VPN, etc.) +- User typing username incorrectly after reset + +### Resolution Indicators +- User confirms successful login +- Account shows last login timestamp updated +- No subsequent lockout or password reset requests +- User able to access all required systems + +### Documentation Links +- Password Policy: Internal KB #PWD-POLICY +- Identity Verification: Internal KB #ID-VERIFY +- Account Management: Internal KB #AD-ACCOUNTS \ No newline at end of file diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 00000000..36ebdcbf --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,17 @@ +# Application +APP_NAME=Troubleshooting Decision Tree +DEBUG=true + +# Database +DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/decision_tree +DATABASE_URL_SYNC=postgresql://postgres:postgres@localhost:5432/decision_tree + +# JWT Settings - CHANGE THESE IN PRODUCTION +# Generate with: openssl rand -hex 32 +SECRET_KEY=your-super-secret-key-change-this-in-production +ALGORITHM=HS256 +ACCESS_TOKEN_EXPIRE_MINUTES=15 +REFRESH_TOKEN_EXPIRE_DAYS=7 + +# CORS +CORS_ORIGINS=["http://localhost:3000","http://localhost:5173"] diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 00000000..fcb1291b --- /dev/null +++ b/backend/README.md @@ -0,0 +1,125 @@ +# Troubleshooting Decision Tree - Backend API + +FastAPI backend for the Troubleshooting Decision Tree application. + +## Quick Start + +### 1. Set up Python environment + +```bash +cd backend +python -m venv venv + +# Windows +venv\Scripts\activate + +# macOS/Linux +source venv/bin/activate + +pip install -r requirements.txt +``` + +### 2. Start PostgreSQL database + +Using Docker: +```bash +docker-compose up -d +``` + +Or install PostgreSQL locally and create a database: +```sql +CREATE DATABASE decision_tree; +``` + +### 3. Configure environment + +Copy the example env file and update as needed: +```bash +cp .env.example .env +``` + +### 4. Run database migrations + +```bash +alembic upgrade head +``` + +### 5. Start the server + +```bash +uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 +``` + +The API will be available at: +- API: http://localhost:8000 +- Docs: http://localhost:8000/api/docs +- ReDoc: http://localhost:8000/api/redoc + +## API Endpoints + +### Authentication +- `POST /api/v1/auth/register` - Register new user +- `POST /api/v1/auth/login` - Login (form data) +- `POST /api/v1/auth/login/json` - Login (JSON body) +- `POST /api/v1/auth/refresh` - Refresh token +- `GET /api/v1/auth/me` - Get current user +- `POST /api/v1/auth/logout` - Logout + +### Trees +- `GET /api/v1/trees` - List all trees +- `GET /api/v1/trees/categories` - List categories +- `GET /api/v1/trees/search?q=query` - Search trees +- `GET /api/v1/trees/{id}` - Get specific tree +- `POST /api/v1/trees` - Create tree (engineer/admin) +- `PUT /api/v1/trees/{id}` - Update tree (engineer/admin) +- `DELETE /api/v1/trees/{id}` - Delete tree (admin) + +### Sessions +- `GET /api/v1/sessions` - List user's sessions +- `GET /api/v1/sessions/{id}` - Get specific session +- `POST /api/v1/sessions` - Start new session +- `PUT /api/v1/sessions/{id}` - Update session +- `POST /api/v1/sessions/{id}/complete` - Complete session +- `POST /api/v1/sessions/{id}/export` - Export session + +## Development + +### Create new migration +```bash +alembic revision --autogenerate -m "description" +``` + +### Run migrations +```bash +alembic upgrade head +``` + +### Rollback migration +```bash +alembic downgrade -1 +``` + +## Project Structure + +``` +backend/ +├── alembic/ # Database migrations +│ └── versions/ +├── app/ +│ ├── api/ +│ │ ├── endpoints/ # API route handlers +│ │ ├── deps.py # Dependencies (auth, etc.) +│ │ └── router.py # Main router +│ ├── core/ +│ │ ├── config.py # Settings +│ │ ├── database.py # DB connection +│ │ └── security.py # JWT, password hashing +│ ├── models/ # SQLAlchemy models +│ ├── schemas/ # Pydantic schemas +│ └── main.py # FastAPI app +├── tests/ +├── alembic.ini +├── docker-compose.yml +├── requirements.txt +└── README.md +``` diff --git a/backend/alembic.ini b/backend/alembic.ini new file mode 100644 index 00000000..ff2839f7 --- /dev/null +++ b/backend/alembic.ini @@ -0,0 +1,42 @@ +[alembic] +script_location = alembic +prepend_sys_path = . +version_path_separator = os + +sqlalchemy.url = postgresql://postgres:postgres@localhost:5432/decision_tree + +[post_write_hooks] + +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/backend/alembic/env.py b/backend/alembic/env.py new file mode 100644 index 00000000..62da8f2f --- /dev/null +++ b/backend/alembic/env.py @@ -0,0 +1,81 @@ +import asyncio +from logging.config import fileConfig + +from sqlalchemy import pool +from sqlalchemy.engine import Connection +from sqlalchemy.ext.asyncio import async_engine_from_config + +from alembic import context + +# Import your models +from app.core.database import Base +from app.models import User, Team, Tree, Session, Attachment +from app.core.config import settings + +# this is the Alembic Config object +config = context.config + +# Override sqlalchemy.url with the sync version for migrations +config.set_main_option("sqlalchemy.url", settings.DATABASE_URL_SYNC) + +# Interpret the config file for Python logging. +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# add your model's MetaData object here +target_metadata = Base.metadata + + +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode.""" + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def do_run_migrations(connection: Connection) -> None: + context.configure(connection=connection, target_metadata=target_metadata) + + with context.begin_transaction(): + context.run_migrations() + + +async def run_async_migrations() -> None: + """Run migrations in 'online' mode with async engine.""" + from sqlalchemy.ext.asyncio import create_async_engine + + connectable = create_async_engine( + settings.DATABASE_URL, + poolclass=pool.NullPool, + ) + + async with connectable.connect() as connection: + await connection.run_sync(do_run_migrations) + + await connectable.dispose() + + +def run_migrations_online() -> None: + """Run migrations in 'online' mode.""" + from sqlalchemy import create_engine + + connectable = create_engine( + settings.DATABASE_URL_SYNC, + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + do_run_migrations(connection) + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/backend/alembic/script.py.mako b/backend/alembic/script.py.mako new file mode 100644 index 00000000..fbc4b07d --- /dev/null +++ b/backend/alembic/script.py.mako @@ -0,0 +1,26 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: Union[str, None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade() -> None: + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + ${downgrades if downgrades else "pass"} diff --git a/backend/alembic/versions/001_initial_schema.py b/backend/alembic/versions/001_initial_schema.py new file mode 100644 index 00000000..0f286afe --- /dev/null +++ b/backend/alembic/versions/001_initial_schema.py @@ -0,0 +1,124 @@ +"""Initial schema + +Revision ID: 001 +Revises: +Create Date: 2026-01-22 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision: str = '001' +down_revision: Union[str, None] = None +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # Create teams table + op.create_table( + 'teams', + sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('gen_random_uuid()'), nullable=False), + sa.Column('name', sa.String(255), nullable=False), + sa.Column('created_at', sa.DateTime(), server_default=sa.text('NOW()'), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + + # Create users table + op.create_table( + 'users', + sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('gen_random_uuid()'), nullable=False), + sa.Column('email', sa.String(255), nullable=False), + sa.Column('password_hash', sa.String(255), nullable=False), + sa.Column('name', sa.String(255), nullable=False), + sa.Column('role', sa.String(50), nullable=False, server_default='engineer'), + sa.Column('team_id', postgresql.UUID(as_uuid=True), nullable=True), + sa.Column('created_at', sa.DateTime(), server_default=sa.text('NOW()'), nullable=True), + sa.Column('last_login', sa.DateTime(), nullable=True), + sa.ForeignKeyConstraint(['team_id'], ['teams.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('email') + ) + op.create_index('idx_users_email', 'users', ['email'], unique=True) + + # Create trees table + op.create_table( + 'trees', + sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('gen_random_uuid()'), nullable=False), + sa.Column('name', sa.String(255), nullable=False), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('category', sa.String(100), nullable=True), + sa.Column('tree_structure', postgresql.JSONB(astext_type=sa.Text()), nullable=False), + sa.Column('author_id', postgresql.UUID(as_uuid=True), nullable=True), + sa.Column('team_id', postgresql.UUID(as_uuid=True), nullable=True), + sa.Column('is_active', sa.Boolean(), server_default='true', nullable=True), + sa.Column('version', sa.Integer(), server_default='1', nullable=True), + sa.Column('created_at', sa.DateTime(), server_default=sa.text('NOW()'), nullable=True), + sa.Column('updated_at', sa.DateTime(), server_default=sa.text('NOW()'), nullable=True), + sa.Column('usage_count', sa.Integer(), server_default='0', nullable=True), + sa.ForeignKeyConstraint(['author_id'], ['users.id'], ), + sa.ForeignKeyConstraint(['team_id'], ['teams.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index('idx_trees_category', 'trees', ['category'], unique=False) + op.create_index('idx_trees_team', 'trees', ['team_id'], unique=False) + # Full-text search index + op.execute( + "CREATE INDEX idx_trees_search ON trees USING gin(to_tsvector('english', coalesce(name, '') || ' ' || coalesce(description, '')))" + ) + + # Create sessions table + op.create_table( + 'sessions', + sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('gen_random_uuid()'), nullable=False), + sa.Column('tree_id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('user_id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('tree_snapshot', postgresql.JSONB(astext_type=sa.Text()), nullable=False), + sa.Column('path_taken', postgresql.JSONB(astext_type=sa.Text()), nullable=False, server_default='[]'), + sa.Column('decisions', postgresql.JSONB(astext_type=sa.Text()), nullable=False, server_default='[]'), + sa.Column('started_at', sa.DateTime(), server_default=sa.text('NOW()'), nullable=True), + sa.Column('completed_at', sa.DateTime(), nullable=True), + sa.Column('ticket_number', sa.String(100), nullable=True), + sa.Column('client_name', sa.String(255), nullable=True), + sa.Column('exported', sa.Boolean(), server_default='false', nullable=True), + sa.ForeignKeyConstraint(['tree_id'], ['trees.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index('idx_sessions_user', 'sessions', ['user_id'], unique=False) + op.create_index('idx_sessions_tree', 'sessions', ['tree_id'], unique=False) + op.create_index('idx_sessions_dates', 'sessions', ['started_at', 'completed_at'], unique=False) + + # Create attachments table + op.create_table( + 'attachments', + sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('gen_random_uuid()'), nullable=False), + sa.Column('session_id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('node_id', sa.String(100), nullable=True), + sa.Column('file_name', sa.String(255), nullable=False), + sa.Column('file_type', sa.String(50), nullable=True), + sa.Column('file_size', sa.Integer(), nullable=True), + sa.Column('storage_path', sa.String(500), nullable=True), + sa.Column('uploaded_at', sa.DateTime(), server_default=sa.text('NOW()'), nullable=True), + sa.ForeignKeyConstraint(['session_id'], ['sessions.id'], ), + sa.PrimaryKeyConstraint('id') + ) + + +def downgrade() -> None: + op.drop_table('attachments') + op.drop_index('idx_sessions_dates', table_name='sessions') + op.drop_index('idx_sessions_tree', table_name='sessions') + op.drop_index('idx_sessions_user', table_name='sessions') + op.drop_table('sessions') + op.drop_index('idx_trees_search', table_name='trees') + op.drop_index('idx_trees_team', table_name='trees') + op.drop_index('idx_trees_category', table_name='trees') + op.drop_table('trees') + op.drop_index('idx_users_email', table_name='users') + op.drop_table('users') + op.drop_table('teams') diff --git a/backend/app/__init__.py b/backend/app/__init__.py new file mode 100644 index 00000000..13e27f44 --- /dev/null +++ b/backend/app/__init__.py @@ -0,0 +1 @@ +# Troubleshooting Decision Tree Backend diff --git a/backend/app/api/__init__.py b/backend/app/api/__init__.py new file mode 100644 index 00000000..9c7f58ef --- /dev/null +++ b/backend/app/api/__init__.py @@ -0,0 +1 @@ +# API module diff --git a/backend/app/api/deps.py b/backend/app/api/deps.py new file mode 100644 index 00000000..fe4fdf87 --- /dev/null +++ b/backend/app/api/deps.py @@ -0,0 +1,82 @@ +from typing import Annotated +from uuid import UUID +from fastapi import Depends, HTTPException, status +from fastapi.security import OAuth2PasswordBearer +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select + +from app.core.database import get_db +from app.core.security import decode_token +from app.models.user import User + +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login") + + +async def get_current_user( + db: Annotated[AsyncSession, Depends(get_db)], + token: Annotated[str, Depends(oauth2_scheme)] +) -> User: + """Get current authenticated user from JWT token.""" + credentials_exception = HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Could not validate credentials", + headers={"WWW-Authenticate": "Bearer"}, + ) + + payload = decode_token(token) + if payload is None: + raise credentials_exception + + token_type = payload.get("type") + if token_type != "access": + raise credentials_exception + + user_id: str = payload.get("sub") + if user_id is None: + raise credentials_exception + + try: + user_uuid = UUID(user_id) + except ValueError: + raise credentials_exception + + result = await db.execute(select(User).where(User.id == user_uuid)) + user = result.scalar_one_or_none() + + if user is None: + raise credentials_exception + + return user + + +async def get_current_active_user( + current_user: Annotated[User, Depends(get_current_user)] +) -> User: + """Ensure user is active (not disabled).""" + # For now, all users are considered active + # Add logic here if you add an is_active field to User + return current_user + + +async def require_admin( + current_user: Annotated[User, Depends(get_current_active_user)] +) -> User: + """Require admin role.""" + if current_user.role != "admin": + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Admin access required" + ) + return current_user + + +async def require_engineer_or_admin( + current_user: Annotated[User, Depends(get_current_active_user)] +) -> User: + """Require engineer or admin role.""" + if current_user.role not in ("admin", "engineer"): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Engineer or admin access required" + ) + return current_user diff --git a/backend/app/api/endpoints/__init__.py b/backend/app/api/endpoints/__init__.py new file mode 100644 index 00000000..42e40146 --- /dev/null +++ b/backend/app/api/endpoints/__init__.py @@ -0,0 +1 @@ +# API endpoints diff --git a/backend/app/api/endpoints/auth.py b/backend/app/api/endpoints/auth.py new file mode 100644 index 00000000..e9a8df03 --- /dev/null +++ b/backend/app/api/endpoints/auth.py @@ -0,0 +1,159 @@ +from datetime import datetime, timezone +from typing import Annotated +from fastapi import APIRouter, Depends, HTTPException, status +from fastapi.security import OAuth2PasswordRequestForm +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select + +from app.core.database import get_db +from app.core.security import ( + verify_password, + get_password_hash, + create_access_token, + create_refresh_token, + decode_token +) +from app.models.user import User +from app.schemas.user import UserCreate, UserResponse, UserLogin +from app.schemas.token import Token +from app.api.deps import get_current_user + +router = APIRouter(prefix="/auth", tags=["authentication"]) + + +@router.post("/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED) +async def register( + user_data: UserCreate, + db: Annotated[AsyncSession, Depends(get_db)] +): + """Register a new user.""" + # Check if email already exists + result = await db.execute(select(User).where(User.email == user_data.email)) + existing_user = result.scalar_one_or_none() + if existing_user: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Email already registered" + ) + + # Create new user + new_user = User( + email=user_data.email, + password_hash=get_password_hash(user_data.password), + name=user_data.name, + role="engineer" # Default role + ) + db.add(new_user) + await db.commit() + await db.refresh(new_user) + + return new_user + + +@router.post("/login", response_model=Token) +async def login( + form_data: Annotated[OAuth2PasswordRequestForm, Depends()], + db: Annotated[AsyncSession, Depends(get_db)] +): + """Login and get access token.""" + # Find user by email + result = await db.execute(select(User).where(User.email == form_data.username)) + user = result.scalar_one_or_none() + + if not user or not verify_password(form_data.password, user.password_hash): + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Incorrect email or password", + headers={"WWW-Authenticate": "Bearer"}, + ) + + # Update last login + user.last_login = datetime.now(timezone.utc) + await db.commit() + + # Create tokens + access_token = create_access_token(data={"sub": str(user.id)}) + refresh_token = create_refresh_token(data={"sub": str(user.id)}) + + return Token( + access_token=access_token, + refresh_token=refresh_token, + token_type="bearer" + ) + + +@router.post("/login/json", response_model=Token) +async def login_json( + credentials: UserLogin, + db: Annotated[AsyncSession, Depends(get_db)] +): + """Login with JSON body (alternative to form data).""" + result = await db.execute(select(User).where(User.email == credentials.email)) + user = result.scalar_one_or_none() + + if not user or not verify_password(credentials.password, user.password_hash): + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Incorrect email or password" + ) + + user.last_login = datetime.now(timezone.utc) + await db.commit() + + access_token = create_access_token(data={"sub": str(user.id)}) + refresh_token = create_refresh_token(data={"sub": str(user.id)}) + + return Token( + access_token=access_token, + refresh_token=refresh_token, + token_type="bearer" + ) + + +@router.post("/refresh", response_model=Token) +async def refresh_token( + refresh_token: str, + db: Annotated[AsyncSession, Depends(get_db)] +): + """Refresh access token using refresh token.""" + payload = decode_token(refresh_token) + if payload is None or payload.get("type") != "refresh": + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid refresh token" + ) + + user_id = payload.get("sub") + result = await db.execute(select(User).where(User.id == user_id)) + user = result.scalar_one_or_none() + + if not user: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="User not found" + ) + + access_token = create_access_token(data={"sub": str(user.id)}) + new_refresh_token = create_refresh_token(data={"sub": str(user.id)}) + + return Token( + access_token=access_token, + refresh_token=new_refresh_token, + token_type="bearer" + ) + + +@router.get("/me", response_model=UserResponse) +async def get_me( + current_user: Annotated[User, Depends(get_current_user)] +): + """Get current authenticated user.""" + return current_user + + +@router.post("/logout") +async def logout(): + """Logout user (client should discard tokens).""" + # JWT tokens are stateless, so logout is handled client-side + # In a production app, you might want to blacklist the token + return {"message": "Successfully logged out"} diff --git a/backend/app/api/endpoints/sessions.py b/backend/app/api/endpoints/sessions.py new file mode 100644 index 00000000..68f065fb --- /dev/null +++ b/backend/app/api/endpoints/sessions.py @@ -0,0 +1,358 @@ +from datetime import datetime, timezone +from typing import Annotated, Optional +from uuid import UUID +from fastapi import APIRouter, Depends, HTTPException, status, Query +from fastapi.responses import PlainTextResponse +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select + +from app.core.database import get_db +from app.models.tree import Tree +from app.models.session import Session +from app.models.user import User +from app.schemas.session import SessionCreate, SessionUpdate, SessionResponse, SessionExport +from app.api.deps import get_current_user + +router = APIRouter(prefix="/sessions", tags=["sessions"]) + + +@router.get("", response_model=list[SessionResponse]) +async def list_sessions( + db: Annotated[AsyncSession, Depends(get_db)], + current_user: Annotated[User, Depends(get_current_user)], + completed: Optional[bool] = Query(None, description="Filter by completion status"), + skip: int = Query(0, ge=0), + limit: int = Query(50, ge=1, le=100) +): + """List user's troubleshooting sessions.""" + query = select(Session).where(Session.user_id == current_user.id) + + if completed is not None: + if completed: + query = query.where(Session.completed_at.isnot(None)) + else: + query = query.where(Session.completed_at.is_(None)) + + query = query.order_by(Session.started_at.desc()) + query = query.offset(skip).limit(limit) + + result = await db.execute(query) + sessions = result.scalars().all() + return sessions + + +@router.get("/{session_id}", response_model=SessionResponse) +async def get_session( + session_id: UUID, + db: Annotated[AsyncSession, Depends(get_db)], + current_user: Annotated[User, Depends(get_current_user)] +): + """Get a specific session.""" + result = await db.execute(select(Session).where(Session.id == session_id)) + session = result.scalar_one_or_none() + + if not session: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Session not found" + ) + + if session.user_id != current_user.id: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="You don't have access to this session" + ) + + return session + + +@router.post("", response_model=SessionResponse, status_code=status.HTTP_201_CREATED) +async def start_session( + session_data: SessionCreate, + db: Annotated[AsyncSession, Depends(get_db)], + current_user: Annotated[User, Depends(get_current_user)] +): + """Start a new troubleshooting session.""" + # Get the tree + result = await db.execute(select(Tree).where(Tree.id == session_data.tree_id)) + tree = result.scalar_one_or_none() + + if not tree: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Tree not found" + ) + + if not tree.is_active: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Cannot start session with inactive tree" + ) + + # Create session with tree snapshot + new_session = Session( + tree_id=tree.id, + user_id=current_user.id, + tree_snapshot=tree.tree_structure, + path_taken=[], + decisions=[], + ticket_number=session_data.ticket_number, + client_name=session_data.client_name + ) + + # Increment tree usage count + tree.usage_count += 1 + + db.add(new_session) + await db.commit() + await db.refresh(new_session) + return new_session + + +@router.put("/{session_id}", response_model=SessionResponse) +async def update_session( + session_id: UUID, + session_data: SessionUpdate, + db: Annotated[AsyncSession, Depends(get_db)], + current_user: Annotated[User, Depends(get_current_user)] +): + """Update session (add decisions, notes, etc.).""" + result = await db.execute(select(Session).where(Session.id == session_id)) + session = result.scalar_one_or_none() + + if not session: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Session not found" + ) + + if session.user_id != current_user.id: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="You don't have access to this session" + ) + + if session.completed_at: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Cannot update a completed session" + ) + + update_data = session_data.model_dump(exclude_unset=True) + + # Convert DecisionRecord objects to dicts if present + if "decisions" in update_data and update_data["decisions"]: + update_data["decisions"] = [ + d.model_dump() if hasattr(d, 'model_dump') else d + for d in update_data["decisions"] + ] + + for field, value in update_data.items(): + setattr(session, field, value) + + await db.commit() + await db.refresh(session) + return session + + +@router.post("/{session_id}/complete", response_model=SessionResponse) +async def complete_session( + session_id: UUID, + db: Annotated[AsyncSession, Depends(get_db)], + current_user: Annotated[User, Depends(get_current_user)] +): + """Mark session as complete.""" + result = await db.execute(select(Session).where(Session.id == session_id)) + session = result.scalar_one_or_none() + + if not session: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Session not found" + ) + + if session.user_id != current_user.id: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="You don't have access to this session" + ) + + if session.completed_at: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Session already completed" + ) + + session.completed_at = datetime.now(timezone.utc) + await db.commit() + await db.refresh(session) + return session + + +@router.post("/{session_id}/export") +async def export_session( + session_id: UUID, + export_options: SessionExport, + db: Annotated[AsyncSession, Depends(get_db)], + current_user: Annotated[User, Depends(get_current_user)] +): + """Export session to formatted notes.""" + result = await db.execute(select(Session).where(Session.id == session_id)) + session = result.scalar_one_or_none() + + if not session: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Session not found" + ) + + if session.user_id != current_user.id: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="You don't have access to this session" + ) + + # Generate export based on format + if export_options.format == "markdown": + content = _generate_markdown_export(session, export_options) + media_type = "text/markdown" + elif export_options.format == "html": + content = _generate_html_export(session, export_options) + media_type = "text/html" + else: # text + content = _generate_text_export(session, export_options) + media_type = "text/plain" + + # Mark as exported + session.exported = True + await db.commit() + + return PlainTextResponse(content=content, media_type=media_type) + + +def _generate_markdown_export(session: Session, options: SessionExport) -> str: + """Generate markdown export.""" + lines = [] + + if options.include_tree_info: + tree_name = session.tree_snapshot.get("name", "Troubleshooting Session") + lines.append(f"# {tree_name}") + lines.append("") + if session.ticket_number: + lines.append(f"**Ticket:** {session.ticket_number}") + if session.client_name: + lines.append(f"**Client:** {session.client_name}") + if options.include_timestamps: + lines.append(f"**Started:** {session.started_at.strftime('%Y-%m-%d %H:%M')}") + if session.completed_at: + lines.append(f"**Completed:** {session.completed_at.strftime('%Y-%m-%d %H:%M')}") + lines.append("") + lines.append("---") + lines.append("") + + lines.append("## Troubleshooting Steps") + lines.append("") + + for i, decision in enumerate(session.decisions, 1): + question = decision.get("question") or decision.get("action_performed", "Step") + answer = decision.get("answer", "") + notes = decision.get("notes", "") + + lines.append(f"### Step {i}: {question}") + if answer: + lines.append(f"**Answer:** {answer}") + if notes: + lines.append(f"**Notes:** {notes}") + if options.include_timestamps and decision.get("timestamp"): + lines.append(f"*{decision['timestamp']}*") + lines.append("") + + return "\n".join(lines) + + +def _generate_text_export(session: Session, options: SessionExport) -> str: + """Generate plain text export.""" + lines = [] + + if options.include_tree_info: + tree_name = session.tree_snapshot.get("name", "Troubleshooting Session") + lines.append(tree_name) + lines.append("=" * len(tree_name)) + if session.ticket_number: + lines.append(f"Ticket: {session.ticket_number}") + if session.client_name: + lines.append(f"Client: {session.client_name}") + if options.include_timestamps: + lines.append(f"Started: {session.started_at.strftime('%Y-%m-%d %H:%M')}") + if session.completed_at: + lines.append(f"Completed: {session.completed_at.strftime('%Y-%m-%d %H:%M')}") + lines.append("") + + lines.append("TROUBLESHOOTING STEPS") + lines.append("-" * 20) + + for i, decision in enumerate(session.decisions, 1): + question = decision.get("question") or decision.get("action_performed", "Step") + answer = decision.get("answer", "") + notes = decision.get("notes", "") + + lines.append(f"\n{i}. {question}") + if answer: + lines.append(f" Answer: {answer}") + if notes: + lines.append(f" Notes: {notes}") + + return "\n".join(lines) + + +def _generate_html_export(session: Session, options: SessionExport) -> str: + """Generate HTML export.""" + tree_name = session.tree_snapshot.get("name", "Troubleshooting Session") + + html = ['', '', '', + '', + f'{tree_name}', + '', + '', ''] + + if options.include_tree_info: + html.append(f'

{tree_name}

') + html.append('
') + if session.ticket_number: + html.append(f'

Ticket: {session.ticket_number}

') + if session.client_name: + html.append(f'

Client: {session.client_name}

') + if options.include_timestamps: + html.append(f'

Started: {session.started_at.strftime("%Y-%m-%d %H:%M")}

') + if session.completed_at: + html.append(f'

Completed: {session.completed_at.strftime("%Y-%m-%d %H:%M")}

') + html.append('
') + + html.append('

Troubleshooting Steps

') + + for i, decision in enumerate(session.decisions, 1): + question = decision.get("question") or decision.get("action_performed", "Step") + answer = decision.get("answer", "") + notes = decision.get("notes", "") + + html.append('
') + html.append(f'

Step {i}: {question}

') + if answer: + html.append(f'

Answer: {answer}

') + if notes: + html.append(f'

Notes: {notes}

') + if options.include_timestamps and decision.get("timestamp"): + html.append(f'

{decision["timestamp"]}

') + html.append('
') + + html.extend(['', '']) + return "\n".join(html) diff --git a/backend/app/api/endpoints/trees.py b/backend/app/api/endpoints/trees.py new file mode 100644 index 00000000..98fad295 --- /dev/null +++ b/backend/app/api/endpoints/trees.py @@ -0,0 +1,193 @@ +from typing import Annotated, Optional +from uuid import UUID +from fastapi import APIRouter, Depends, HTTPException, status, Query +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select, func, or_ + +from app.core.database import get_db +from app.models.tree import Tree +from app.models.user import User +from app.schemas.tree import TreeCreate, TreeUpdate, TreeResponse, TreeListResponse +from app.api.deps import get_current_user, require_engineer_or_admin, require_admin + +router = APIRouter(prefix="/trees", tags=["trees"]) + + +@router.get("", response_model=list[TreeListResponse]) +async def list_trees( + db: Annotated[AsyncSession, Depends(get_db)], + current_user: Annotated[User, Depends(get_current_user)], + category: Optional[str] = Query(None, description="Filter by category"), + is_active: Optional[bool] = Query(None, description="Filter by active status"), + skip: int = Query(0, ge=0), + limit: int = Query(100, ge=1, le=100) +): + """List all trees with optional filters.""" + query = select(Tree) + + # Apply filters + if category: + query = query.where(Tree.category == category) + if is_active is not None: + query = query.where(Tree.is_active == is_active) + + # Only show active trees or trees owned by user (for now) + # Later, add team-based filtering + query = query.where( + or_( + Tree.is_active == True, + Tree.author_id == current_user.id + ) + ) + + query = query.order_by(Tree.usage_count.desc(), Tree.updated_at.desc()) + query = query.offset(skip).limit(limit) + + result = await db.execute(query) + trees = result.scalars().all() + return trees + + +@router.get("/categories", response_model=list[str]) +async def list_categories( + db: Annotated[AsyncSession, Depends(get_db)], + current_user: Annotated[User, Depends(get_current_user)] +): + """List all unique categories.""" + query = select(Tree.category).where( + Tree.category.isnot(None), + Tree.is_active == True + ).distinct() + result = await db.execute(query) + categories = [row[0] for row in result.all() if row[0]] + return sorted(categories) + + +@router.get("/search", response_model=list[TreeListResponse]) +async def search_trees( + db: Annotated[AsyncSession, Depends(get_db)], + current_user: Annotated[User, Depends(get_current_user)], + q: str = Query(..., min_length=2, description="Search query"), + limit: int = Query(20, ge=1, le=50) +): + """Full-text search trees by name and description.""" + # Using PostgreSQL full-text search + search_vector = func.to_tsvector('english', func.coalesce(Tree.name, '') + ' ' + func.coalesce(Tree.description, '')) + search_query = func.plainto_tsquery('english', q) + + query = select(Tree).where( + Tree.is_active == True, + search_vector.op('@@')(search_query) + ).order_by( + func.ts_rank(search_vector, search_query).desc() + ).limit(limit) + + result = await db.execute(query) + trees = result.scalars().all() + return trees + + +@router.get("/{tree_id}", response_model=TreeResponse) +async def get_tree( + tree_id: UUID, + db: Annotated[AsyncSession, Depends(get_db)], + current_user: Annotated[User, Depends(get_current_user)] +): + """Get a specific tree by ID.""" + result = await db.execute(select(Tree).where(Tree.id == tree_id)) + tree = result.scalar_one_or_none() + + if not tree: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Tree not found" + ) + + # Check access: tree must be active OR user is the author + if not tree.is_active and tree.author_id != current_user.id: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="You don't have access to this tree" + ) + + return tree + + +@router.post("", response_model=TreeResponse, status_code=status.HTTP_201_CREATED) +async def create_tree( + tree_data: TreeCreate, + db: Annotated[AsyncSession, Depends(get_db)], + current_user: Annotated[User, Depends(require_engineer_or_admin)] +): + """Create a new tree (engineers and admins only).""" + new_tree = Tree( + name=tree_data.name, + description=tree_data.description, + category=tree_data.category, + tree_structure=tree_data.tree_structure, + author_id=current_user.id, + team_id=current_user.team_id + ) + db.add(new_tree) + await db.commit() + await db.refresh(new_tree) + return new_tree + + +@router.put("/{tree_id}", response_model=TreeResponse) +async def update_tree( + tree_id: UUID, + tree_data: TreeUpdate, + db: Annotated[AsyncSession, Depends(get_db)], + current_user: Annotated[User, Depends(require_engineer_or_admin)] +): + """Update an existing tree (engineers and admins only).""" + result = await db.execute(select(Tree).where(Tree.id == tree_id)) + tree = result.scalar_one_or_none() + + if not tree: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Tree not found" + ) + + # Check if user can edit: must be author or admin + if tree.author_id != current_user.id and current_user.role != "admin": + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="You can only edit your own trees" + ) + + # Update fields + update_data = tree_data.model_dump(exclude_unset=True) + for field, value in update_data.items(): + setattr(tree, field, value) + + # Increment version if tree structure changed + if "tree_structure" in update_data: + tree.version += 1 + + await db.commit() + await db.refresh(tree) + return tree + + +@router.delete("/{tree_id}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_tree( + tree_id: UUID, + db: Annotated[AsyncSession, Depends(get_db)], + current_user: Annotated[User, Depends(require_admin)] +): + """Soft delete a tree (admin only). Sets is_active to False.""" + result = await db.execute(select(Tree).where(Tree.id == tree_id)) + tree = result.scalar_one_or_none() + + if not tree: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Tree not found" + ) + + tree.is_active = False + await db.commit() + return None diff --git a/backend/app/api/router.py b/backend/app/api/router.py new file mode 100644 index 00000000..e1c5936c --- /dev/null +++ b/backend/app/api/router.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter +from app.api.endpoints import auth, trees, sessions + +api_router = APIRouter() + +api_router.include_router(auth.router) +api_router.include_router(trees.router) +api_router.include_router(sessions.router) diff --git a/backend/app/core/__init__.py b/backend/app/core/__init__.py new file mode 100644 index 00000000..3e83c630 --- /dev/null +++ b/backend/app/core/__init__.py @@ -0,0 +1 @@ +# Core module diff --git a/backend/app/core/config.py b/backend/app/core/config.py new file mode 100644 index 00000000..8c8e1bc7 --- /dev/null +++ b/backend/app/core/config.py @@ -0,0 +1,32 @@ +from pydantic_settings import BaseSettings +from typing import Optional + + +class Settings(BaseSettings): + # Application + APP_NAME: str = "Troubleshooting Decision Tree" + DEBUG: bool = False + API_V1_PREFIX: str = "/api/v1" + + # Database + DATABASE_URL: str = "postgresql+asyncpg://postgres:postgres@localhost:5432/decision_tree" + DATABASE_URL_SYNC: str = "postgresql://postgres:postgres@localhost:5432/decision_tree" + + # JWT Settings + SECRET_KEY: str = "your-secret-key-change-in-production-use-openssl-rand-hex-32" + ALGORITHM: str = "HS256" + ACCESS_TOKEN_EXPIRE_MINUTES: int = 15 + REFRESH_TOKEN_EXPIRE_DAYS: int = 7 + + # Security + BCRYPT_ROUNDS: int = 12 + + # CORS + CORS_ORIGINS: list[str] = ["http://localhost:3000", "http://localhost:5173"] + + class Config: + env_file = ".env" + case_sensitive = True + + +settings = Settings() diff --git a/backend/app/core/database.py b/backend/app/core/database.py new file mode 100644 index 00000000..cee22d64 --- /dev/null +++ b/backend/app/core/database.py @@ -0,0 +1,37 @@ +from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker +from sqlalchemy.orm import DeclarativeBase +from .config import settings + +# Create async engine +engine = create_async_engine( + settings.DATABASE_URL, + echo=settings.DEBUG, + future=True +) + +# Create async session factory +async_session_maker = async_sessionmaker( + engine, + class_=AsyncSession, + expire_on_commit=False +) + + +class Base(DeclarativeBase): + """Base class for all database models.""" + pass + + +async def get_db() -> AsyncSession: + """Dependency to get database session.""" + async with async_session_maker() as session: + try: + yield session + finally: + await session.close() + + +async def init_db(): + """Initialize database tables.""" + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) diff --git a/backend/app/core/security.py b/backend/app/core/security.py new file mode 100644 index 00000000..9d1928b3 --- /dev/null +++ b/backend/app/core/security.py @@ -0,0 +1,47 @@ +from datetime import datetime, timedelta, timezone +from typing import Optional +from jose import JWTError, jwt +from passlib.context import CryptContext +from .config import settings + +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + + +def verify_password(plain_password: str, hashed_password: str) -> bool: + """Verify a password against its hash.""" + return pwd_context.verify(plain_password, hashed_password) + + +def get_password_hash(password: str) -> str: + """Hash a password.""" + return pwd_context.hash(password, rounds=settings.BCRYPT_ROUNDS) + + +def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str: + """Create a JWT access token.""" + to_encode = data.copy() + if expires_delta: + expire = datetime.now(timezone.utc) + expires_delta + else: + expire = datetime.now(timezone.utc) + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) + to_encode.update({"exp": expire, "type": "access"}) + encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) + return encoded_jwt + + +def create_refresh_token(data: dict) -> str: + """Create a JWT refresh token.""" + to_encode = data.copy() + expire = datetime.now(timezone.utc) + timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS) + to_encode.update({"exp": expire, "type": "refresh"}) + encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) + return encoded_jwt + + +def decode_token(token: str) -> Optional[dict]: + """Decode and validate a JWT token.""" + try: + payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]) + return payload + except JWTError: + return None diff --git a/backend/app/main.py b/backend/app/main.py new file mode 100644 index 00000000..0a66b9e0 --- /dev/null +++ b/backend/app/main.py @@ -0,0 +1,57 @@ +from contextlib import asynccontextmanager +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +from app.core.config import settings +from app.core.database import init_db +from app.api.router import api_router + + +@asynccontextmanager +async def lifespan(app: FastAPI): + """Application lifespan handler.""" + # Startup + # Note: In production, use Alembic migrations instead of init_db + # await init_db() + yield + # Shutdown + pass + + +app = FastAPI( + title=settings.APP_NAME, + description="A troubleshooting decision tree application for MSP engineers", + version="1.0.0", + docs_url="/api/docs", + redoc_url="/api/redoc", + openapi_url="/api/openapi.json", + lifespan=lifespan +) + +# Configure CORS +app.add_middleware( + CORSMiddleware, + allow_origins=settings.CORS_ORIGINS, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Include API router +app.include_router(api_router, prefix=settings.API_V1_PREFIX) + + +@app.get("/") +async def root(): + """Root endpoint.""" + return { + "message": "Troubleshooting Decision Tree API", + "docs": "/api/docs", + "version": "1.0.0" + } + + +@app.get("/health") +async def health_check(): + """Health check endpoint.""" + return {"status": "healthy"} diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py new file mode 100644 index 00000000..9c752f54 --- /dev/null +++ b/backend/app/models/__init__.py @@ -0,0 +1,7 @@ +from .user import User +from .team import Team +from .tree import Tree +from .session import Session +from .attachment import Attachment + +__all__ = ["User", "Team", "Tree", "Session", "Attachment"] diff --git a/backend/app/models/attachment.py b/backend/app/models/attachment.py new file mode 100644 index 00000000..6ff4deb8 --- /dev/null +++ b/backend/app/models/attachment.py @@ -0,0 +1,34 @@ +import uuid +from datetime import datetime +from typing import Optional +from sqlalchemy import String, Integer, DateTime, ForeignKey +from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy.dialects.postgresql import UUID +from app.core.database import Base + + +class Attachment(Base): + __tablename__ = "attachments" + + id: Mapped[uuid.UUID] = mapped_column( + UUID(as_uuid=True), + primary_key=True, + default=uuid.uuid4 + ) + session_id: Mapped[uuid.UUID] = mapped_column( + UUID(as_uuid=True), + ForeignKey("sessions.id"), + nullable=False + ) + node_id: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) + file_name: Mapped[str] = mapped_column(String(255), nullable=False) + file_type: Mapped[Optional[str]] = mapped_column(String(50), nullable=True) + file_size: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) + storage_path: Mapped[Optional[str]] = mapped_column(String(500), nullable=True) + uploaded_at: Mapped[datetime] = mapped_column( + DateTime, + default=datetime.utcnow + ) + + # Relationships + session: Mapped["Session"] = relationship("Session", back_populates="attachments") diff --git a/backend/app/models/session.py b/backend/app/models/session.py new file mode 100644 index 00000000..11da5574 --- /dev/null +++ b/backend/app/models/session.py @@ -0,0 +1,46 @@ +import uuid +from datetime import datetime +from typing import Optional, Any +from sqlalchemy import String, DateTime, ForeignKey, Boolean +from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy.dialects.postgresql import UUID, JSONB +from app.core.database import Base + + +class Session(Base): + __tablename__ = "sessions" + + id: Mapped[uuid.UUID] = mapped_column( + UUID(as_uuid=True), + primary_key=True, + default=uuid.uuid4 + ) + tree_id: Mapped[uuid.UUID] = mapped_column( + UUID(as_uuid=True), + ForeignKey("trees.id"), + nullable=False, + index=True + ) + user_id: Mapped[uuid.UUID] = mapped_column( + UUID(as_uuid=True), + ForeignKey("users.id"), + nullable=False, + index=True + ) + tree_snapshot: Mapped[dict[str, Any]] = mapped_column(JSONB, nullable=False) + path_taken: Mapped[list[str]] = mapped_column(JSONB, nullable=False, default=list) + decisions: Mapped[list[dict[str, Any]]] = mapped_column(JSONB, nullable=False, default=list) + started_at: Mapped[datetime] = mapped_column( + DateTime, + default=datetime.utcnow, + index=True + ) + completed_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True, index=True) + ticket_number: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) + client_name: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) + exported: Mapped[bool] = mapped_column(Boolean, default=False) + + # Relationships + tree: Mapped["Tree"] = relationship("Tree", back_populates="sessions") + user: Mapped["User"] = relationship("User", back_populates="sessions") + attachments: Mapped[list["Attachment"]] = relationship("Attachment", back_populates="session") diff --git a/backend/app/models/team.py b/backend/app/models/team.py new file mode 100644 index 00000000..0ed896ed --- /dev/null +++ b/backend/app/models/team.py @@ -0,0 +1,25 @@ +import uuid +from datetime import datetime +from sqlalchemy import String, DateTime +from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy.dialects.postgresql import UUID +from app.core.database import Base + + +class Team(Base): + __tablename__ = "teams" + + id: Mapped[uuid.UUID] = mapped_column( + UUID(as_uuid=True), + primary_key=True, + default=uuid.uuid4 + ) + name: Mapped[str] = mapped_column(String(255), nullable=False) + created_at: Mapped[datetime] = mapped_column( + DateTime, + default=datetime.utcnow + ) + + # Relationships + users: Mapped[list["User"]] = relationship("User", back_populates="team") + trees: Mapped[list["Tree"]] = relationship("Tree", back_populates="team") diff --git a/backend/app/models/tree.py b/backend/app/models/tree.py new file mode 100644 index 00000000..6ebc2daa --- /dev/null +++ b/backend/app/models/tree.py @@ -0,0 +1,51 @@ +import uuid +from datetime import datetime +from typing import Optional, Any +from sqlalchemy import String, Text, DateTime, ForeignKey, Boolean, Integer, Index +from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy.dialects.postgresql import UUID, JSONB +from app.core.database import Base + + +class Tree(Base): + __tablename__ = "trees" + + id: Mapped[uuid.UUID] = mapped_column( + UUID(as_uuid=True), + primary_key=True, + default=uuid.uuid4 + ) + name: Mapped[str] = mapped_column(String(255), nullable=False) + description: Mapped[Optional[str]] = mapped_column(Text, nullable=True) + category: Mapped[Optional[str]] = mapped_column(String(100), nullable=True, index=True) + tree_structure: Mapped[dict[str, Any]] = mapped_column(JSONB, nullable=False) + author_id: Mapped[Optional[uuid.UUID]] = mapped_column( + UUID(as_uuid=True), + ForeignKey("users.id"), + nullable=True + ) + team_id: Mapped[Optional[uuid.UUID]] = mapped_column( + UUID(as_uuid=True), + ForeignKey("teams.id"), + nullable=True, + index=True + ) + is_active: Mapped[bool] = mapped_column(Boolean, default=True) + version: Mapped[int] = mapped_column(Integer, default=1) + created_at: Mapped[datetime] = mapped_column( + DateTime, + default=datetime.utcnow + ) + updated_at: Mapped[datetime] = mapped_column( + DateTime, + default=datetime.utcnow, + onupdate=datetime.utcnow + ) + usage_count: Mapped[int] = mapped_column(Integer, default=0) + + # Relationships + author: Mapped[Optional["User"]] = relationship("User", back_populates="trees") + team: Mapped[Optional["Team"]] = relationship("Team", back_populates="trees") + sessions: Mapped[list["Session"]] = relationship("Session", back_populates="tree") + + # Full-text search index will be created in migration diff --git a/backend/app/models/user.py b/backend/app/models/user.py new file mode 100644 index 00000000..87882b22 --- /dev/null +++ b/backend/app/models/user.py @@ -0,0 +1,36 @@ +import uuid +from datetime import datetime +from typing import Optional +from sqlalchemy import String, DateTime, ForeignKey +from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy.dialects.postgresql import UUID +from app.core.database import Base + + +class User(Base): + __tablename__ = "users" + + id: Mapped[uuid.UUID] = mapped_column( + UUID(as_uuid=True), + primary_key=True, + default=uuid.uuid4 + ) + email: Mapped[str] = mapped_column(String(255), unique=True, nullable=False, index=True) + password_hash: Mapped[str] = mapped_column(String(255), nullable=False) + name: Mapped[str] = mapped_column(String(255), nullable=False) + role: Mapped[str] = mapped_column(String(50), nullable=False, default="engineer") # admin, engineer, viewer + team_id: Mapped[Optional[uuid.UUID]] = mapped_column( + UUID(as_uuid=True), + ForeignKey("teams.id"), + nullable=True + ) + created_at: Mapped[datetime] = mapped_column( + DateTime, + default=datetime.utcnow + ) + last_login: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True) + + # Relationships + team: Mapped[Optional["Team"]] = relationship("Team", back_populates="users") + trees: Mapped[list["Tree"]] = relationship("Tree", back_populates="author") + sessions: Mapped[list["Session"]] = relationship("Session", back_populates="user") diff --git a/backend/app/schemas/__init__.py b/backend/app/schemas/__init__.py new file mode 100644 index 00000000..e08aa5ec --- /dev/null +++ b/backend/app/schemas/__init__.py @@ -0,0 +1,11 @@ +from .user import UserCreate, UserUpdate, UserResponse, UserLogin +from .token import Token, TokenPayload +from .tree import TreeCreate, TreeUpdate, TreeResponse, TreeListResponse +from .session import SessionCreate, SessionUpdate, SessionResponse, SessionExport, DecisionRecord + +__all__ = [ + "UserCreate", "UserUpdate", "UserResponse", "UserLogin", + "Token", "TokenPayload", + "TreeCreate", "TreeUpdate", "TreeResponse", "TreeListResponse", + "SessionCreate", "SessionUpdate", "SessionResponse", "SessionExport", "DecisionRecord" +] diff --git a/backend/app/schemas/session.py b/backend/app/schemas/session.py new file mode 100644 index 00000000..c653144f --- /dev/null +++ b/backend/app/schemas/session.py @@ -0,0 +1,51 @@ +from datetime import datetime +from typing import Optional, Any +from uuid import UUID +from pydantic import BaseModel, Field + + +class DecisionRecord(BaseModel): + node_id: str + question: Optional[str] = None + answer: Optional[str] = None + action_performed: Optional[str] = None + notes: Optional[str] = None + automation_used: Optional[bool] = False + timestamp: datetime + attachments: list[str] = Field(default_factory=list) + + +class SessionCreate(BaseModel): + tree_id: UUID + ticket_number: Optional[str] = Field(None, max_length=100) + client_name: Optional[str] = Field(None, max_length=255) + + +class SessionUpdate(BaseModel): + path_taken: Optional[list[str]] = None + decisions: Optional[list[DecisionRecord]] = None + ticket_number: Optional[str] = Field(None, max_length=100) + client_name: Optional[str] = Field(None, max_length=255) + + +class SessionResponse(BaseModel): + id: UUID + tree_id: UUID + user_id: UUID + tree_snapshot: dict[str, Any] + path_taken: list[str] + decisions: list[dict[str, Any]] + started_at: datetime + completed_at: Optional[datetime] = None + ticket_number: Optional[str] = None + client_name: Optional[str] = None + exported: bool + + class Config: + from_attributes = True + + +class SessionExport(BaseModel): + format: str = Field(default="markdown", pattern="^(text|markdown|html)$") + include_timestamps: bool = True + include_tree_info: bool = True diff --git a/backend/app/schemas/token.py b/backend/app/schemas/token.py new file mode 100644 index 00000000..1730c6e7 --- /dev/null +++ b/backend/app/schemas/token.py @@ -0,0 +1,14 @@ +from typing import Optional +from pydantic import BaseModel + + +class Token(BaseModel): + access_token: str + refresh_token: str + token_type: str = "bearer" + + +class TokenPayload(BaseModel): + sub: Optional[str] = None + exp: Optional[int] = None + type: Optional[str] = None diff --git a/backend/app/schemas/tree.py b/backend/app/schemas/tree.py new file mode 100644 index 00000000..84fc590e --- /dev/null +++ b/backend/app/schemas/tree.py @@ -0,0 +1,52 @@ +from datetime import datetime +from typing import Optional, Any +from uuid import UUID +from pydantic import BaseModel, Field + + +class TreeBase(BaseModel): + name: str = Field(..., min_length=1, max_length=255) + description: Optional[str] = None + category: Optional[str] = Field(None, max_length=100) + + +class TreeCreate(TreeBase): + tree_structure: dict[str, Any] = Field(..., description="The decision tree structure in JSON format") + + +class TreeUpdate(BaseModel): + name: Optional[str] = Field(None, min_length=1, max_length=255) + description: Optional[str] = None + category: Optional[str] = Field(None, max_length=100) + tree_structure: Optional[dict[str, Any]] = None + is_active: Optional[bool] = None + + +class TreeResponse(TreeBase): + id: UUID + tree_structure: dict[str, Any] + author_id: Optional[UUID] = None + team_id: Optional[UUID] = None + is_active: bool + version: int + created_at: datetime + updated_at: datetime + usage_count: int + + class Config: + from_attributes = True + + +class TreeListResponse(BaseModel): + id: UUID + name: str + description: Optional[str] = None + category: Optional[str] = None + is_active: bool + version: int + usage_count: int + created_at: datetime + updated_at: datetime + + class Config: + from_attributes = True diff --git a/backend/app/schemas/user.py b/backend/app/schemas/user.py new file mode 100644 index 00000000..6b985704 --- /dev/null +++ b/backend/app/schemas/user.py @@ -0,0 +1,34 @@ +from datetime import datetime +from typing import Optional +from uuid import UUID +from pydantic import BaseModel, EmailStr, Field + + +class UserBase(BaseModel): + email: EmailStr + name: str = Field(..., min_length=1, max_length=255) + + +class UserCreate(UserBase): + password: str = Field(..., min_length=10, description="Password must be at least 10 characters") + + +class UserUpdate(BaseModel): + name: Optional[str] = Field(None, min_length=1, max_length=255) + email: Optional[EmailStr] = None + + +class UserLogin(BaseModel): + email: EmailStr + password: str + + +class UserResponse(UserBase): + id: UUID + role: str + team_id: Optional[UUID] = None + created_at: datetime + last_login: Optional[datetime] = None + + class Config: + from_attributes = True diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml new file mode 100644 index 00000000..6b819b55 --- /dev/null +++ b/backend/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + db: + image: postgres:16-alpine + container_name: decision_tree_db + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: decision_tree + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 5s + retries: 5 + +volumes: + postgres_data: diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 00000000..5e16077a --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,22 @@ +# FastAPI and server +fastapi==0.109.2 +uvicorn[standard]==0.27.1 + +# Database +sqlalchemy==2.0.25 +asyncpg==0.29.0 +psycopg2-binary==2.9.9 +alembic==1.13.1 + +# Authentication +python-jose[cryptography]==3.3.0 +passlib[bcrypt]==1.7.4 +python-multipart==0.0.9 + +# Validation and settings +pydantic==2.6.1 +pydantic-settings==2.1.0 +email-validator==2.1.0 + +# Utilities +python-dotenv==1.0.1