Fix backend: add passlib/bcrypt, fix datetime timezone issues

This commit is contained in:
Michael Chihlas
2026-01-23 12:17:18 -05:00
parent c823531a36
commit fa632da6bb
5 changed files with 244 additions and 8 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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 ###

View File

@@ -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")

View File

@@ -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