From fa632da6bb3df1a009cb17e359e525d8a62b7d0d Mon Sep 17 00:00:00 2001 From: Michael Chihlas Date: Fri, 23 Jan 2026 12:17:18 -0500 Subject: [PATCH] Fix backend: add passlib/bcrypt, fix datetime timezone issues --- 01-PROJECT-OVERVIEW.md | 20 ++- 02-TECHNICAL-ARCHITECTURE.md | 42 ++++- .../7e00fa3c75c9_fix_datetime_timezone.py | 148 ++++++++++++++++++ backend/app/models/user.py | 12 +- backend/requirements-windows.txt | 30 ++++ 5 files changed, 244 insertions(+), 8 deletions(-) create mode 100644 backend/alembic/versions/7e00fa3c75c9_fix_datetime_timezone.py create mode 100644 backend/requirements-windows.txt diff --git a/01-PROJECT-OVERVIEW.md b/01-PROJECT-OVERVIEW.md index 7002782f..69cf7af2 100644 --- a/01-PROJECT-OVERVIEW.md +++ b/01-PROJECT-OVERVIEW.md @@ -1,11 +1,15 @@ # 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) @@ -13,7 +17,9 @@ MSP engineers like Michael face constant context switching between diverse techn - 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 @@ -21,9 +27,11 @@ An intelligent decision tree system that: - 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 @@ -31,14 +39,17 @@ An intelligent decision tree system that: - Reduction in repeated troubleshooting attempts ### Target Users + **Primary:** Senior Systems Engineers at MSPs managing Windows Server, Active Directory, Citrix, networking equipment -**Secondary:** +**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 @@ -47,11 +58,13 @@ An intelligent decision tree system that: 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 @@ -60,6 +73,7 @@ An intelligent decision tree system that: - **Integrations:** Paid add-ons for ConnectWise, Kaseya, etc. ### Name Ideas (To Workshop) + - TroubleTree - DecisionPath - MSP Navigator @@ -71,7 +85,9 @@ An intelligent decision tree system that: - 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) @@ -81,6 +97,7 @@ An intelligent decision tree system that: 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 @@ -88,6 +105,7 @@ Purpose-built for technical troubleshooting with automation integration and auto - **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 diff --git a/02-TECHNICAL-ARCHITECTURE.md b/02-TECHNICAL-ARCHITECTURE.md index 25a54982..00a7bcc5 100644 --- a/02-TECHNICAL-ARCHITECTURE.md +++ b/02-TECHNICAL-ARCHITECTURE.md @@ -3,6 +3,7 @@ ## System Architecture ### High-Level Architecture + ``` ┌─────────────────────────────────────────────────────┐ │ Frontend (React/Vue) │ @@ -41,7 +42,9 @@ ## 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) @@ -50,7 +53,9 @@ - **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 @@ -59,26 +64,33 @@ - **Migration:** Alembic ### Database + **Primary Choice: PostgreSQL** + - **Pros:** JSON/JSONB support perfect for tree storage, reliable, scalable -- **Schema Design:** +- **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 @@ -99,6 +111,7 @@ ### Database Schema #### Users Table + ```sql CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), @@ -113,6 +126,7 @@ CREATE TABLE users ( ``` #### Teams Table + ```sql CREATE TABLE teams ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), @@ -122,6 +136,7 @@ CREATE TABLE teams ( ``` #### Trees Table + ```sql CREATE TABLE trees ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), @@ -144,6 +159,7 @@ CREATE INDEX idx_trees_search ON trees USING gin(to_tsvector('english', name || ``` #### Sessions Table + ```sql CREATE TABLE sessions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), @@ -165,6 +181,7 @@ 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(), @@ -368,6 +385,7 @@ CREATE TABLE attachments ( ## API Endpoints ### Authentication + ``` POST /api/auth/register - Register new user POST /api/auth/login - Login @@ -377,6 +395,7 @@ POST /api/auth/refresh - Refresh JWT token ``` ### Trees + ``` GET /api/trees - List all trees (with filters) GET /api/trees/:id - Get specific tree @@ -388,6 +407,7 @@ GET /api/trees/search - Full-text search trees ``` ### Sessions + ``` GET /api/sessions - List user's sessions GET /api/sessions/:id - Get specific session @@ -398,6 +418,7 @@ 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 @@ -406,6 +427,7 @@ DELETE /api/attachments/:id - Delete attachment ``` ### Teams (Phase 2) + ``` GET /api/teams - List teams POST /api/teams - Create team (admin only) @@ -415,6 +437,7 @@ 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 @@ -423,6 +446,7 @@ 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 @@ -432,6 +456,7 @@ 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 @@ -439,6 +464,7 @@ GET /api/automation/history - Automation execution history - Account lockout after failed attempts ### Data Protection + - All passwords hashed with bcrypt (cost factor 12) - Sensitive data encrypted at rest - HTTPS only in production @@ -447,6 +473,7 @@ GET /api/automation/history - Automation execution history - 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) @@ -454,6 +481,7 @@ GET /api/automation/history - Automation execution history - Signed URLs with expiration ### API Security + - Rate limiting (100 requests/min per user) - Request size limits - API versioning (/api/v1/...) @@ -462,18 +490,21 @@ GET /api/automation/history - Automation execution history ## 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 @@ -483,12 +514,14 @@ GET /api/automation/history - Automation execution history ## 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 @@ -496,6 +529,7 @@ GET /api/automation/history - Automation execution history - System resource usage ### Error Tracking + - Sentry integration for error tracking - User-friendly error messages - Automatic error reporting with context @@ -503,18 +537,21 @@ GET /api/automation/history - Automation execution history ## 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 @@ -523,18 +560,21 @@ GET /api/automation/history - Automation execution history ## 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 diff --git a/backend/alembic/versions/7e00fa3c75c9_fix_datetime_timezone.py b/backend/alembic/versions/7e00fa3c75c9_fix_datetime_timezone.py new file mode 100644 index 00000000..88129bbf --- /dev/null +++ b/backend/alembic/versions/7e00fa3c75c9_fix_datetime_timezone.py @@ -0,0 +1,148 @@ +"""Fix datetime timezone + +Revision ID: 7e00fa3c75c9 +Revises: 001 +Create Date: 2026-01-23 11:51:47.640123 + +""" +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 = '7e00fa3c75c9' +down_revision: Union[str, None] = '001' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('attachments', 'uploaded_at', + existing_type=postgresql.TIMESTAMP(), + nullable=False, + existing_server_default=sa.text('now()')) + op.alter_column('sessions', 'started_at', + existing_type=postgresql.TIMESTAMP(), + nullable=False, + existing_server_default=sa.text('now()')) + op.alter_column('sessions', 'exported', + existing_type=sa.BOOLEAN(), + nullable=False, + existing_server_default=sa.text('false')) + 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.create_index(op.f('ix_sessions_completed_at'), 'sessions', ['completed_at'], unique=False) + op.create_index(op.f('ix_sessions_started_at'), 'sessions', ['started_at'], unique=False) + op.create_index(op.f('ix_sessions_tree_id'), 'sessions', ['tree_id'], unique=False) + op.create_index(op.f('ix_sessions_user_id'), 'sessions', ['user_id'], unique=False) + op.alter_column('teams', 'created_at', + existing_type=postgresql.TIMESTAMP(), + nullable=False, + existing_server_default=sa.text('now()')) + op.alter_column('trees', 'is_active', + existing_type=sa.BOOLEAN(), + nullable=False, + existing_server_default=sa.text('true')) + op.alter_column('trees', 'version', + existing_type=sa.INTEGER(), + nullable=False, + existing_server_default=sa.text('1')) + op.alter_column('trees', 'created_at', + existing_type=postgresql.TIMESTAMP(), + nullable=False, + existing_server_default=sa.text('now()')) + op.alter_column('trees', 'updated_at', + existing_type=postgresql.TIMESTAMP(), + nullable=False, + existing_server_default=sa.text('now()')) + op.alter_column('trees', 'usage_count', + existing_type=sa.INTEGER(), + nullable=False, + existing_server_default=sa.text('0')) + op.drop_index('idx_trees_category', table_name='trees') + op.drop_index('idx_trees_search', table_name='trees', postgresql_using='gin') + op.drop_index('idx_trees_team', table_name='trees') + op.create_index(op.f('ix_trees_category'), 'trees', ['category'], unique=False) + op.create_index(op.f('ix_trees_team_id'), 'trees', ['team_id'], unique=False) + op.alter_column('users', 'created_at', + existing_type=postgresql.TIMESTAMP(), + type_=sa.DateTime(timezone=True), + nullable=False, + existing_server_default=sa.text('now()')) + op.alter_column('users', 'last_login', + existing_type=postgresql.TIMESTAMP(), + type_=sa.DateTime(timezone=True), + existing_nullable=True) + op.drop_index('idx_users_email', table_name='users') + op.drop_constraint('users_email_key', 'users', type_='unique') + op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f('ix_users_email'), table_name='users') + op.create_unique_constraint('users_email_key', 'users', ['email']) + op.create_index('idx_users_email', 'users', ['email'], unique=True) + op.alter_column('users', 'last_login', + existing_type=sa.DateTime(timezone=True), + type_=postgresql.TIMESTAMP(), + existing_nullable=True) + op.alter_column('users', 'created_at', + existing_type=sa.DateTime(timezone=True), + type_=postgresql.TIMESTAMP(), + nullable=True, + existing_server_default=sa.text('now()')) + op.drop_index(op.f('ix_trees_team_id'), table_name='trees') + op.drop_index(op.f('ix_trees_category'), table_name='trees') + op.create_index('idx_trees_team', 'trees', ['team_id'], unique=False) + op.create_index('idx_trees_search', 'trees', [sa.text("to_tsvector('english'::regconfig, (COALESCE(name, ''::character varying)::text || ' '::text) || COALESCE(description, ''::text))")], unique=False, postgresql_using='gin') + op.create_index('idx_trees_category', 'trees', ['category'], unique=False) + op.alter_column('trees', 'usage_count', + existing_type=sa.INTEGER(), + nullable=True, + existing_server_default=sa.text('0')) + op.alter_column('trees', 'updated_at', + existing_type=postgresql.TIMESTAMP(), + nullable=True, + existing_server_default=sa.text('now()')) + op.alter_column('trees', 'created_at', + existing_type=postgresql.TIMESTAMP(), + nullable=True, + existing_server_default=sa.text('now()')) + op.alter_column('trees', 'version', + existing_type=sa.INTEGER(), + nullable=True, + existing_server_default=sa.text('1')) + op.alter_column('trees', 'is_active', + existing_type=sa.BOOLEAN(), + nullable=True, + existing_server_default=sa.text('true')) + op.alter_column('teams', 'created_at', + existing_type=postgresql.TIMESTAMP(), + nullable=True, + existing_server_default=sa.text('now()')) + op.drop_index(op.f('ix_sessions_user_id'), table_name='sessions') + op.drop_index(op.f('ix_sessions_tree_id'), table_name='sessions') + op.drop_index(op.f('ix_sessions_started_at'), table_name='sessions') + op.drop_index(op.f('ix_sessions_completed_at'), table_name='sessions') + 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) + op.alter_column('sessions', 'exported', + existing_type=sa.BOOLEAN(), + nullable=True, + existing_server_default=sa.text('false')) + op.alter_column('sessions', 'started_at', + existing_type=postgresql.TIMESTAMP(), + nullable=True, + existing_server_default=sa.text('now()')) + op.alter_column('attachments', 'uploaded_at', + existing_type=postgresql.TIMESTAMP(), + nullable=True, + existing_server_default=sa.text('now()')) + # ### end Alembic commands ### diff --git a/backend/app/models/user.py b/backend/app/models/user.py index 87882b22..04e7844a 100644 --- a/backend/app/models/user.py +++ b/backend/app/models/user.py @@ -1,5 +1,5 @@ import uuid -from datetime import datetime +from datetime import datetime, timezone from typing import Optional from sqlalchemy import String, DateTime, ForeignKey from sqlalchemy.orm import Mapped, mapped_column, relationship @@ -18,19 +18,19 @@ class User(Base): 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 + role: Mapped[str] = mapped_column(String(50), nullable=False, default="engineer") 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 + DateTime(timezone=True), + default=lambda: datetime.now(timezone.utc) ) - last_login: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True) + last_login: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), 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") + sessions: Mapped[list["Session"]] = relationship("Session", back_populates="user") \ No newline at end of file diff --git a/backend/requirements-windows.txt b/backend/requirements-windows.txt new file mode 100644 index 00000000..a344fe01 --- /dev/null +++ b/backend/requirements-windows.txt @@ -0,0 +1,30 @@ +# FastAPI and dependencies +fastapi==0.109.2 +uvicorn[standard]==0.27.1 +python-multipart==0.0.9 + +# Pydantic with pre-built wheels +pydantic==2.6.1 +pydantic-settings==2.1.0 +pydantic-core==2.16.2 +annotated-types==0.6.0 + +# Database +sqlalchemy[asyncio]==2.0.27 +asyncpg==0.29.0 +alembic==1.13.1 + +# Authentication +python-jose[cryptography]==3.3.0 +passlib[bcrypt]==1.7.4 +bcrypt==4.1.2 + +# Security +cryptography==42.0.2 + +# Email validation +email-validator==2.1.0.post1 + +# Others +starlette==0.36.3 +typing-extensions==4.9.0