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 # Troubleshooting Decision Tree Application
## Project Overview ## Project Overview
### Vision ### Vision
A decision tree troubleshooting application designed for MSP engineers to transform diagnostic processes into clean, professional documentation automatically. A decision tree troubleshooting application designed for MSP engineers to transform diagnostic processes into clean, professional documentation automatically.
### Core Problem ### 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: 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 - Takes 15-25 minutes to regain full focus
- Creates cognitive overhead and attention residue - Creates cognitive overhead and attention residue
- Contributes to burnout (research-backed) - 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 - Results in lost tribal knowledge
### Solution ### Solution
An intelligent decision tree system that: An intelligent decision tree system that:
- Guides engineers through proven troubleshooting paths - Guides engineers through proven troubleshooting paths
- Captures decisions and notes automatically - Captures decisions and notes automatically
- Generates professional ticket documentation - Generates professional ticket documentation
@@ -21,9 +27,11 @@ An intelligent decision tree system that:
- Reduces cognitive load during high-stress situations - Reduces cognitive load during high-stress situations
### Success Criteria ### Success Criteria
**3-Month Goal:** Michael uses this tool for 50% of his tickets **3-Month Goal:** Michael uses this tool for 50% of his tickets
**Key Metrics:** **Key Metrics:**
- Time saved per ticket - Time saved per ticket
- Documentation quality improvement - Documentation quality improvement
- Reduction in "what did I do?" moments - Reduction in "what did I do?" moments
@@ -31,14 +39,17 @@ An intelligent decision tree system that:
- Reduction in repeated troubleshooting attempts - Reduction in repeated troubleshooting attempts
### Target Users ### Target Users
**Primary:** Senior Systems Engineers at MSPs managing Windows Server, Active Directory, Citrix, networking equipment **Primary:** Senior Systems Engineers at MSPs managing Windows Server, Active Directory, Citrix, networking equipment
**Secondary:** **Secondary:**
- Junior engineers needing guided troubleshooting - Junior engineers needing guided troubleshooting
- Onsite technicians following remote engineer instructions - Onsite technicians following remote engineer instructions
- MSP teams wanting standardized procedures - MSP teams wanting standardized procedures
### Key Differentiators ### Key Differentiators
1. **Automatic documentation generation** - No separate note-taking step 1. **Automatic documentation generation** - No separate note-taking step
2. **On-the-fly customization** - Add custom branches when encountering edge cases 2. **On-the-fly customization** - Add custom branches when encountering edge cases
3. **Learning system** - Tracks common paths, suggests optimizations 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 6. **Team collaboration** - Controlled authorship with shared access
### Potential Market ### Potential Market
- 30,000+ MSPs in North America alone - 30,000+ MSPs in North America alone
- Average MSP has 15-50 technical staff - Average MSP has 15-50 technical staff
- Adjacent markets: Internal IT teams, DevOps, Technical Support - Adjacent markets: Internal IT teams, DevOps, Technical Support
### Monetization Possibilities ### Monetization Possibilities
- **Free Tier:** Personal use, limited trees - **Free Tier:** Personal use, limited trees
- **Pro Tier:** Team sharing, unlimited trees, analytics - **Pro Tier:** Team sharing, unlimited trees, analytics
- **Enterprise:** API access, SSO, custom branding, white-label - **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. - **Integrations:** Paid add-ons for ConnectWise, Kaseya, etc.
### Name Ideas (To Workshop) ### Name Ideas (To Workshop)
- TroubleTree - TroubleTree
- DecisionPath - DecisionPath
- MSP Navigator - MSP Navigator
@@ -71,7 +85,9 @@ An intelligent decision tree system that:
- DiagPath - DiagPath
### Competitive Landscape ### Competitive Landscape
**Current Solutions:** **Current Solutions:**
- Static runbooks/wiki pages (not interactive) - Static runbooks/wiki pages (not interactive)
- Flowchart tools (not designed for real-time troubleshooting) - Flowchart tools (not designed for real-time troubleshooting)
- Ticketing system templates (limited branching logic) - 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. Purpose-built for technical troubleshooting with automation integration and automatic documentation generation.
### Technology Philosophy ### Technology Philosophy
- **Web-first:** Accessible anywhere, no installation - **Web-first:** Accessible anywhere, no installation
- **Progressive enhancement:** Works offline, syncs when online - **Progressive enhancement:** Works offline, syncs when online
- **API-driven:** Backend separate from frontend for flexibility - **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 - **Open-source friendly:** Consider open-sourcing core, monetize integrations/hosting
### Project Status ### Project Status
**Current Phase:** Planning and Architecture **Current Phase:** Planning and Architecture
**Next Phase:** MVP Development (Weeks 1-3) **Next Phase:** MVP Development (Weeks 1-3)
**Target MVP Date:** 3 weeks from project start **Target MVP Date:** 3 weeks from project start

View File

@@ -3,6 +3,7 @@
## System Architecture ## System Architecture
### High-Level Architecture ### High-Level Architecture
``` ```
┌─────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────┐
│ Frontend (React/Vue) │ │ Frontend (React/Vue) │
@@ -41,7 +42,9 @@
## Tech Stack ## Tech Stack
### Frontend ### Frontend
**Primary Choice: React** **Primary Choice: React**
- **Pros:** Large ecosystem, excellent offline support (PWA), familiar to most developers - **Pros:** Large ecosystem, excellent offline support (PWA), familiar to most developers
- **Alternatives:** Vue.js (simpler), Svelte (faster) - **Alternatives:** Vue.js (simpler), Svelte (faster)
- **UI Framework:** Tailwind CSS + shadcn/ui (clean, professional look) - **UI Framework:** Tailwind CSS + shadcn/ui (clean, professional look)
@@ -50,7 +53,9 @@
- **Offline:** Service Workers + IndexedDB for offline tree caching - **Offline:** Service Workers + IndexedDB for offline tree caching
### Backend ### Backend
**Primary Choice: Python FastAPI** **Primary Choice: Python FastAPI**
- **Pros:** Modern, fast, async support, automatic API docs, matches Michael's learning path - **Pros:** Modern, fast, async support, automatic API docs, matches Michael's learning path
- **Alternatives:** Flask (simpler but less performant), Django (heavier) - **Alternatives:** Flask (simpler but less performant), Django (heavier)
- **Authentication:** JWT tokens + httpOnly cookies - **Authentication:** JWT tokens + httpOnly cookies
@@ -59,7 +64,9 @@
- **Migration:** Alembic - **Migration:** Alembic
### Database ### Database
**Primary Choice: PostgreSQL** **Primary Choice: PostgreSQL**
- **Pros:** JSON/JSONB support perfect for tree storage, reliable, scalable - **Pros:** JSON/JSONB support perfect for tree storage, reliable, scalable
- **Schema Design:** - **Schema Design:**
- Hybrid approach: Relational for users/sessions, JSONB for tree structure - Hybrid approach: Relational for users/sessions, JSONB for tree structure
@@ -67,18 +74,23 @@
- Indexes on frequently queried fields - Indexes on frequently queried fields
### File Storage ### File Storage
**Primary Choice: S3-compatible storage** **Primary Choice: S3-compatible storage**
- **Development:** MinIO (self-hosted, S3-compatible) - **Development:** MinIO (self-hosted, S3-compatible)
- **Production:** AWS S3 or DigitalOcean Spaces - **Production:** AWS S3 or DigitalOcean Spaces
- **Strategy:** Pre-signed URLs for uploads, CDN for delivery - **Strategy:** Pre-signed URLs for uploads, CDN for delivery
### Hosting ### Hosting
**Development:** **Development:**
- Frontend: Local dev server (Vite) - Frontend: Local dev server (Vite)
- Backend: Local Python server - Backend: Local Python server
- Database: Docker PostgreSQL - Database: Docker PostgreSQL
**Production Options:** **Production Options:**
1. **Simple Start:** Railway or Render (full-stack hosting) 1. **Simple Start:** Railway or Render (full-stack hosting)
- Cost: ~$10-20/month - Cost: ~$10-20/month
- Pros: Easy deployment, managed databases - Pros: Easy deployment, managed databases
@@ -99,6 +111,7 @@
### Database Schema ### Database Schema
#### Users Table #### Users Table
```sql ```sql
CREATE TABLE users ( CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
@@ -113,6 +126,7 @@ CREATE TABLE users (
``` ```
#### Teams Table #### Teams Table
```sql ```sql
CREATE TABLE teams ( CREATE TABLE teams (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
@@ -122,6 +136,7 @@ CREATE TABLE teams (
``` ```
#### Trees Table #### Trees Table
```sql ```sql
CREATE TABLE trees ( CREATE TABLE trees (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), 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 #### Sessions Table
```sql ```sql
CREATE TABLE sessions ( CREATE TABLE sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), 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 #### Attachments Table
```sql ```sql
CREATE TABLE attachments ( CREATE TABLE attachments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
@@ -368,6 +385,7 @@ CREATE TABLE attachments (
## API Endpoints ## API Endpoints
### Authentication ### Authentication
``` ```
POST /api/auth/register - Register new user POST /api/auth/register - Register new user
POST /api/auth/login - Login POST /api/auth/login - Login
@@ -377,6 +395,7 @@ POST /api/auth/refresh - Refresh JWT token
``` ```
### Trees ### Trees
``` ```
GET /api/trees - List all trees (with filters) GET /api/trees - List all trees (with filters)
GET /api/trees/:id - Get specific tree GET /api/trees/:id - Get specific tree
@@ -388,6 +407,7 @@ GET /api/trees/search - Full-text search trees
``` ```
### Sessions ### Sessions
``` ```
GET /api/sessions - List user's sessions GET /api/sessions - List user's sessions
GET /api/sessions/:id - Get specific session GET /api/sessions/:id - Get specific session
@@ -398,6 +418,7 @@ POST /api/sessions/:id/export - Export session to formatted notes
``` ```
### Attachments ### Attachments
``` ```
POST /api/sessions/:id/attachments - Upload attachment POST /api/sessions/:id/attachments - Upload attachment
GET /api/sessions/:id/attachments - List attachments GET /api/sessions/:id/attachments - List attachments
@@ -406,6 +427,7 @@ DELETE /api/attachments/:id - Delete attachment
``` ```
### Teams (Phase 2) ### Teams (Phase 2)
``` ```
GET /api/teams - List teams GET /api/teams - List teams
POST /api/teams - Create team (admin only) POST /api/teams - Create team (admin only)
@@ -415,6 +437,7 @@ DELETE /api/teams/:id/members/:user_id - Remove team member
``` ```
### Analytics (Phase 3) ### Analytics (Phase 3)
``` ```
GET /api/analytics/trees/:id/usage - Tree usage statistics GET /api/analytics/trees/:id/usage - Tree usage statistics
GET /api/analytics/trees/:id/paths - Common paths taken 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) ### Automation (Phase 4)
``` ```
GET /api/automation/scripts - List available automation scripts GET /api/automation/scripts - List available automation scripts
POST /api/automation/execute - Execute automation script POST /api/automation/execute - Execute automation script
@@ -432,6 +456,7 @@ GET /api/automation/history - Automation execution history
## Security Considerations ## Security Considerations
### Authentication & Authorization ### Authentication & Authorization
- JWT tokens with short expiry (15 min access, 7 day refresh) - JWT tokens with short expiry (15 min access, 7 day refresh)
- Role-based access control (RBAC) - Role-based access control (RBAC)
- Password requirements: min 10 chars, complexity - Password requirements: min 10 chars, complexity
@@ -439,6 +464,7 @@ GET /api/automation/history - Automation execution history
- Account lockout after failed attempts - Account lockout after failed attempts
### Data Protection ### Data Protection
- All passwords hashed with bcrypt (cost factor 12) - All passwords hashed with bcrypt (cost factor 12)
- Sensitive data encrypted at rest - Sensitive data encrypted at rest
- HTTPS only in production - HTTPS only in production
@@ -447,6 +473,7 @@ GET /api/automation/history - Automation execution history
- XSS prevention (input sanitization, CSP headers) - XSS prevention (input sanitization, CSP headers)
### File Upload Security ### File Upload Security
- File type validation (whitelist only) - File type validation (whitelist only)
- File size limits (10MB per file) - File size limits (10MB per file)
- Virus scanning (ClamAV integration for Phase 3) - Virus scanning (ClamAV integration for Phase 3)
@@ -454,6 +481,7 @@ GET /api/automation/history - Automation execution history
- Signed URLs with expiration - Signed URLs with expiration
### API Security ### API Security
- Rate limiting (100 requests/min per user) - Rate limiting (100 requests/min per user)
- Request size limits - Request size limits
- API versioning (/api/v1/...) - API versioning (/api/v1/...)
@@ -462,18 +490,21 @@ GET /api/automation/history - Automation execution history
## Performance Considerations ## Performance Considerations
### Database ### Database
- Indexes on frequently queried fields - Indexes on frequently queried fields
- Connection pooling - Connection pooling
- Query optimization (EXPLAIN ANALYZE) - Query optimization (EXPLAIN ANALYZE)
- Consider read replicas for Phase 3+ - Consider read replicas for Phase 3+
### Caching Strategy ### Caching Strategy
- Redis for session storage (Phase 2) - Redis for session storage (Phase 2)
- Cache frequently accessed trees - Cache frequently accessed trees
- CDN for static assets - CDN for static assets
- Browser caching headers - Browser caching headers
### Frontend Performance ### Frontend Performance
- Code splitting (lazy load routes) - Code splitting (lazy load routes)
- Tree data cached in IndexedDB - Tree data cached in IndexedDB
- Debounced search inputs - Debounced search inputs
@@ -483,12 +514,14 @@ GET /api/automation/history - Automation execution history
## Monitoring & Observability ## Monitoring & Observability
### Logging ### Logging
- Structured logging (JSON format) - Structured logging (JSON format)
- Log levels: DEBUG, INFO, WARNING, ERROR, CRITICAL - Log levels: DEBUG, INFO, WARNING, ERROR, CRITICAL
- Request ID tracking across services - Request ID tracking across services
- User action auditing - User action auditing
### Metrics (Phase 3) ### Metrics (Phase 3)
- API response times - API response times
- Database query performance - Database query performance
- Error rates - Error rates
@@ -496,6 +529,7 @@ GET /api/automation/history - Automation execution history
- System resource usage - System resource usage
### Error Tracking ### Error Tracking
- Sentry integration for error tracking - Sentry integration for error tracking
- User-friendly error messages - User-friendly error messages
- Automatic error reporting with context - Automatic error reporting with context
@@ -503,18 +537,21 @@ GET /api/automation/history - Automation execution history
## Deployment Strategy ## Deployment Strategy
### CI/CD Pipeline ### CI/CD Pipeline
1. **Development:** Local development with hot reload 1. **Development:** Local development with hot reload
2. **Testing:** Automated tests on PR 2. **Testing:** Automated tests on PR
3. **Staging:** Auto-deploy to staging environment 3. **Staging:** Auto-deploy to staging environment
4. **Production:** Manual approval → deploy 4. **Production:** Manual approval → deploy
### Database Migrations ### Database Migrations
- Alembic for schema migrations - Alembic for schema migrations
- Backwards-compatible changes - Backwards-compatible changes
- Rollback capability - Rollback capability
- Test migrations on staging first - Test migrations on staging first
### Backup Strategy ### Backup Strategy
- Automated daily database backups - Automated daily database backups
- Point-in-time recovery capability - Point-in-time recovery capability
- File storage replication - File storage replication
@@ -523,18 +560,21 @@ GET /api/automation/history - Automation execution history
## Future Technical Considerations ## Future Technical Considerations
### Scalability ### Scalability
- Horizontal scaling (multiple app servers) - Horizontal scaling (multiple app servers)
- Database sharding (by team_id) - Database sharding (by team_id)
- Microservices architecture (if needed) - Microservices architecture (if needed)
- Message queue for async tasks (Celery + Redis) - Message queue for async tasks (Celery + Redis)
### Mobile Apps ### Mobile Apps
- React Native for iOS/Android - React Native for iOS/Android
- Shared API backend - Shared API backend
- Offline-first architecture - Offline-first architecture
- Push notifications for team updates - Push notifications for team updates
### AI/ML Integration (Phase 5+) ### AI/ML Integration (Phase 5+)
- Suggest next steps based on past sessions - Suggest next steps based on past sessions
- Auto-categorize tickets - Auto-categorize tickets
- Predict resolution time - 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 import uuid
from datetime import datetime from datetime import datetime, timezone
from typing import Optional from typing import Optional
from sqlalchemy import String, DateTime, ForeignKey from sqlalchemy import String, DateTime, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
@@ -18,17 +18,17 @@ class User(Base):
email: Mapped[str] = mapped_column(String(255), unique=True, nullable=False, index=True) email: Mapped[str] = mapped_column(String(255), unique=True, nullable=False, index=True)
password_hash: Mapped[str] = mapped_column(String(255), nullable=False) password_hash: Mapped[str] = mapped_column(String(255), nullable=False)
name: 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( team_id: Mapped[Optional[uuid.UUID]] = mapped_column(
UUID(as_uuid=True), UUID(as_uuid=True),
ForeignKey("teams.id"), ForeignKey("teams.id"),
nullable=True nullable=True
) )
created_at: Mapped[datetime] = mapped_column( created_at: Mapped[datetime] = mapped_column(
DateTime, DateTime(timezone=True),
default=datetime.utcnow 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 # Relationships
team: Mapped[Optional["Team"]] = relationship("Team", back_populates="users") team: Mapped[Optional["Team"]] = relationship("Team", back_populates="users")

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