Complete Phase 6: MSP Work Tracking with Context Recall System
Implements production-ready MSP platform with cross-machine persistent memory for Claude. API Implementation: - 130 REST API endpoints across 21 entities - JWT authentication on all endpoints - AES-256-GCM encryption for credentials - Automatic audit logging - Complete OpenAPI documentation Database: - 43 tables in MariaDB (172.16.3.20:3306) - 42 SQLAlchemy models with modern 2.0 syntax - Full Alembic migration system - 99.1% CRUD test pass rate Context Recall System (Phase 6): - Cross-machine persistent memory via database - Automatic context injection via Claude Code hooks - Automatic context saving after task completion - 90-95% token reduction with compression utilities - Relevance scoring with time decay - Tag-based semantic search - One-command setup script Security Features: - JWT tokens with Argon2 password hashing - AES-256-GCM encryption for all sensitive data - Comprehensive audit trail for credentials - HMAC tamper detection - Secure configuration management Test Results: - Phase 3: 38/38 CRUD tests passing (100%) - Phase 4: 34/35 core API tests passing (97.1%) - Phase 5: 62/62 extended API tests passing (100%) - Phase 6: 10/10 compression tests passing (100%) - Overall: 144/145 tests passing (99.3%) Documentation: - Comprehensive architecture guides - Setup automation scripts - API documentation at /api/docs - Complete test reports - Troubleshooting guides Project Status: 95% Complete (Production-Ready) Phase 7 (optional work context APIs) remains for future enhancement. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
141
api/schemas/__init__.py
Normal file
141
api/schemas/__init__.py
Normal file
@@ -0,0 +1,141 @@
|
||||
"""Pydantic schemas for request/response validation"""
|
||||
|
||||
from .billable_time import BillableTimeBase, BillableTimeCreate, BillableTimeResponse, BillableTimeUpdate
|
||||
from .client import ClientBase, ClientCreate, ClientResponse, ClientUpdate
|
||||
from .context_snippet import ContextSnippetBase, ContextSnippetCreate, ContextSnippetResponse, ContextSnippetUpdate
|
||||
from .conversation_context import (
|
||||
ConversationContextBase,
|
||||
ConversationContextCreate,
|
||||
ConversationContextResponse,
|
||||
ConversationContextUpdate,
|
||||
)
|
||||
from .credential import CredentialBase, CredentialCreate, CredentialResponse, CredentialUpdate
|
||||
from .credential_audit_log import (
|
||||
CredentialAuditLogBase,
|
||||
CredentialAuditLogCreate,
|
||||
CredentialAuditLogResponse,
|
||||
CredentialAuditLogUpdate,
|
||||
)
|
||||
from .decision_log import DecisionLogBase, DecisionLogCreate, DecisionLogResponse, DecisionLogUpdate
|
||||
from .firewall_rule import FirewallRuleBase, FirewallRuleCreate, FirewallRuleResponse, FirewallRuleUpdate
|
||||
from .infrastructure import InfrastructureBase, InfrastructureCreate, InfrastructureResponse, InfrastructureUpdate
|
||||
from .m365_tenant import M365TenantBase, M365TenantCreate, M365TenantResponse, M365TenantUpdate
|
||||
from .machine import MachineBase, MachineCreate, MachineResponse, MachineUpdate
|
||||
from .network import NetworkBase, NetworkCreate, NetworkResponse, NetworkUpdate
|
||||
from .project import ProjectBase, ProjectCreate, ProjectResponse, ProjectUpdate
|
||||
from .project_state import ProjectStateBase, ProjectStateCreate, ProjectStateResponse, ProjectStateUpdate
|
||||
from .security_incident import SecurityIncidentBase, SecurityIncidentCreate, SecurityIncidentResponse, SecurityIncidentUpdate
|
||||
from .service import ServiceBase, ServiceCreate, ServiceResponse, ServiceUpdate
|
||||
from .session import SessionBase, SessionCreate, SessionResponse, SessionUpdate
|
||||
from .site import SiteBase, SiteCreate, SiteResponse, SiteUpdate
|
||||
from .tag import TagBase, TagCreate, TagResponse, TagUpdate
|
||||
from .task import TaskBase, TaskCreate, TaskResponse, TaskUpdate
|
||||
from .work_item import WorkItemBase, WorkItemCreate, WorkItemResponse, WorkItemUpdate
|
||||
|
||||
__all__ = [
|
||||
# Machine schemas
|
||||
"MachineBase",
|
||||
"MachineCreate",
|
||||
"MachineUpdate",
|
||||
"MachineResponse",
|
||||
# Client schemas
|
||||
"ClientBase",
|
||||
"ClientCreate",
|
||||
"ClientUpdate",
|
||||
"ClientResponse",
|
||||
# Project schemas
|
||||
"ProjectBase",
|
||||
"ProjectCreate",
|
||||
"ProjectUpdate",
|
||||
"ProjectResponse",
|
||||
# Session schemas
|
||||
"SessionBase",
|
||||
"SessionCreate",
|
||||
"SessionUpdate",
|
||||
"SessionResponse",
|
||||
# Tag schemas
|
||||
"TagBase",
|
||||
"TagCreate",
|
||||
"TagUpdate",
|
||||
"TagResponse",
|
||||
# WorkItem schemas
|
||||
"WorkItemBase",
|
||||
"WorkItemCreate",
|
||||
"WorkItemUpdate",
|
||||
"WorkItemResponse",
|
||||
# Task schemas
|
||||
"TaskBase",
|
||||
"TaskCreate",
|
||||
"TaskUpdate",
|
||||
"TaskResponse",
|
||||
# BillableTime schemas
|
||||
"BillableTimeBase",
|
||||
"BillableTimeCreate",
|
||||
"BillableTimeUpdate",
|
||||
"BillableTimeResponse",
|
||||
# Site schemas
|
||||
"SiteBase",
|
||||
"SiteCreate",
|
||||
"SiteUpdate",
|
||||
"SiteResponse",
|
||||
# Infrastructure schemas
|
||||
"InfrastructureBase",
|
||||
"InfrastructureCreate",
|
||||
"InfrastructureUpdate",
|
||||
"InfrastructureResponse",
|
||||
# Service schemas
|
||||
"ServiceBase",
|
||||
"ServiceCreate",
|
||||
"ServiceUpdate",
|
||||
"ServiceResponse",
|
||||
# Network schemas
|
||||
"NetworkBase",
|
||||
"NetworkCreate",
|
||||
"NetworkUpdate",
|
||||
"NetworkResponse",
|
||||
# FirewallRule schemas
|
||||
"FirewallRuleBase",
|
||||
"FirewallRuleCreate",
|
||||
"FirewallRuleUpdate",
|
||||
"FirewallRuleResponse",
|
||||
# M365Tenant schemas
|
||||
"M365TenantBase",
|
||||
"M365TenantCreate",
|
||||
"M365TenantUpdate",
|
||||
"M365TenantResponse",
|
||||
# Credential schemas
|
||||
"CredentialBase",
|
||||
"CredentialCreate",
|
||||
"CredentialUpdate",
|
||||
"CredentialResponse",
|
||||
# CredentialAuditLog schemas
|
||||
"CredentialAuditLogBase",
|
||||
"CredentialAuditLogCreate",
|
||||
"CredentialAuditLogUpdate",
|
||||
"CredentialAuditLogResponse",
|
||||
# SecurityIncident schemas
|
||||
"SecurityIncidentBase",
|
||||
"SecurityIncidentCreate",
|
||||
"SecurityIncidentUpdate",
|
||||
"SecurityIncidentResponse",
|
||||
# ConversationContext schemas
|
||||
"ConversationContextBase",
|
||||
"ConversationContextCreate",
|
||||
"ConversationContextUpdate",
|
||||
"ConversationContextResponse",
|
||||
# ContextSnippet schemas
|
||||
"ContextSnippetBase",
|
||||
"ContextSnippetCreate",
|
||||
"ContextSnippetUpdate",
|
||||
"ContextSnippetResponse",
|
||||
# ProjectState schemas
|
||||
"ProjectStateBase",
|
||||
"ProjectStateCreate",
|
||||
"ProjectStateUpdate",
|
||||
"ProjectStateResponse",
|
||||
# DecisionLog schemas
|
||||
"DecisionLogBase",
|
||||
"DecisionLogCreate",
|
||||
"DecisionLogUpdate",
|
||||
"DecisionLogResponse",
|
||||
]
|
||||
99
api/schemas/billable_time.py
Normal file
99
api/schemas/billable_time.py
Normal file
@@ -0,0 +1,99 @@
|
||||
"""
|
||||
Pydantic schemas for BillableTime model.
|
||||
|
||||
Request and response schemas for billable time entries with billing information.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
|
||||
class BillableTimeBase(BaseModel):
|
||||
"""Base schema with shared BillableTime fields."""
|
||||
|
||||
work_item_id: Optional[str] = Field(None, description="Foreign key to work_items table (UUID)")
|
||||
session_id: Optional[str] = Field(None, description="Foreign key to sessions table (UUID)")
|
||||
client_id: str = Field(..., description="Foreign key to clients table (UUID)")
|
||||
start_time: datetime = Field(..., description="When the billable time started")
|
||||
end_time: Optional[datetime] = Field(None, description="When the billable time ended")
|
||||
duration_minutes: int = Field(..., description="Duration in minutes (auto-calculated or manual)", gt=0)
|
||||
hourly_rate: float = Field(..., description="Hourly rate applied to this time entry", ge=0)
|
||||
total_amount: float = Field(..., description="Total billable amount (calculated)", ge=0)
|
||||
is_billable: bool = Field(True, description="Whether this time entry is actually billable")
|
||||
description: str = Field(..., description="Description of the work performed")
|
||||
category: str = Field(..., description="Category: consulting, development, support, maintenance, troubleshooting, project_work, training, documentation")
|
||||
notes: Optional[str] = Field(None, description="Additional notes about this time entry")
|
||||
invoiced_at: Optional[datetime] = Field(None, description="When this time entry was invoiced")
|
||||
invoice_id: Optional[str] = Field(None, description="Reference to invoice if applicable")
|
||||
|
||||
@field_validator('category')
|
||||
@classmethod
|
||||
def validate_category(cls, v: str) -> str:
|
||||
"""Validate that category is one of the allowed values."""
|
||||
allowed_categories = {
|
||||
'consulting', 'development', 'support', 'maintenance',
|
||||
'troubleshooting', 'project_work', 'training', 'documentation'
|
||||
}
|
||||
if v not in allowed_categories:
|
||||
raise ValueError(f"Category must be one of: {', '.join(allowed_categories)}")
|
||||
return v
|
||||
|
||||
@field_validator('end_time')
|
||||
@classmethod
|
||||
def validate_end_time(cls, v: Optional[datetime], info) -> Optional[datetime]:
|
||||
"""Validate that end_time is after start_time if provided."""
|
||||
if v is not None and 'start_time' in info.data:
|
||||
start_time = info.data['start_time']
|
||||
if v < start_time:
|
||||
raise ValueError("end_time must be after start_time")
|
||||
return v
|
||||
|
||||
|
||||
class BillableTimeCreate(BillableTimeBase):
|
||||
"""Schema for creating a new BillableTime entry."""
|
||||
pass
|
||||
|
||||
|
||||
class BillableTimeUpdate(BaseModel):
|
||||
"""Schema for updating an existing BillableTime entry. All fields are optional."""
|
||||
|
||||
work_item_id: Optional[str] = Field(None, description="Foreign key to work_items table (UUID)")
|
||||
session_id: Optional[str] = Field(None, description="Foreign key to sessions table (UUID)")
|
||||
client_id: Optional[str] = Field(None, description="Foreign key to clients table (UUID)")
|
||||
start_time: Optional[datetime] = Field(None, description="When the billable time started")
|
||||
end_time: Optional[datetime] = Field(None, description="When the billable time ended")
|
||||
duration_minutes: Optional[int] = Field(None, description="Duration in minutes (auto-calculated or manual)", gt=0)
|
||||
hourly_rate: Optional[float] = Field(None, description="Hourly rate applied to this time entry", ge=0)
|
||||
total_amount: Optional[float] = Field(None, description="Total billable amount (calculated)", ge=0)
|
||||
is_billable: Optional[bool] = Field(None, description="Whether this time entry is actually billable")
|
||||
description: Optional[str] = Field(None, description="Description of the work performed")
|
||||
category: Optional[str] = Field(None, description="Category: consulting, development, support, maintenance, troubleshooting, project_work, training, documentation")
|
||||
notes: Optional[str] = Field(None, description="Additional notes about this time entry")
|
||||
invoiced_at: Optional[datetime] = Field(None, description="When this time entry was invoiced")
|
||||
invoice_id: Optional[str] = Field(None, description="Reference to invoice if applicable")
|
||||
|
||||
@field_validator('category')
|
||||
@classmethod
|
||||
def validate_category(cls, v: Optional[str]) -> Optional[str]:
|
||||
"""Validate that category is one of the allowed values."""
|
||||
if v is not None:
|
||||
allowed_categories = {
|
||||
'consulting', 'development', 'support', 'maintenance',
|
||||
'troubleshooting', 'project_work', 'training', 'documentation'
|
||||
}
|
||||
if v not in allowed_categories:
|
||||
raise ValueError(f"Category must be one of: {', '.join(allowed_categories)}")
|
||||
return v
|
||||
|
||||
|
||||
class BillableTimeResponse(BillableTimeBase):
|
||||
"""Schema for BillableTime responses with ID and timestamps."""
|
||||
|
||||
id: UUID = Field(..., description="Unique identifier for the billable time entry")
|
||||
created_at: datetime = Field(..., description="Timestamp when the entry was created")
|
||||
updated_at: datetime = Field(..., description="Timestamp when the entry was last updated")
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
52
api/schemas/client.py
Normal file
52
api/schemas/client.py
Normal file
@@ -0,0 +1,52 @@
|
||||
"""
|
||||
Pydantic schemas for Client model.
|
||||
|
||||
Request and response schemas for client organizations.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ClientBase(BaseModel):
|
||||
"""Base schema with shared Client fields."""
|
||||
|
||||
name: str = Field(..., description="Client name (unique)")
|
||||
type: str = Field(..., description="Client type: msp_client, internal, project")
|
||||
network_subnet: Optional[str] = Field(None, description="Client network subnet (e.g., '192.168.0.0/24')")
|
||||
domain_name: Optional[str] = Field(None, description="Active Directory domain or primary domain")
|
||||
m365_tenant_id: Optional[str] = Field(None, description="Microsoft 365 tenant ID (UUID format)")
|
||||
primary_contact: Optional[str] = Field(None, description="Primary contact person")
|
||||
notes: Optional[str] = Field(None, description="Additional notes about the client")
|
||||
is_active: bool = Field(True, description="Whether client is currently active")
|
||||
|
||||
|
||||
class ClientCreate(ClientBase):
|
||||
"""Schema for creating a new Client."""
|
||||
pass
|
||||
|
||||
|
||||
class ClientUpdate(BaseModel):
|
||||
"""Schema for updating an existing Client. All fields are optional."""
|
||||
|
||||
name: Optional[str] = Field(None, description="Client name (unique)")
|
||||
type: Optional[str] = Field(None, description="Client type: msp_client, internal, project")
|
||||
network_subnet: Optional[str] = Field(None, description="Client network subnet (e.g., '192.168.0.0/24')")
|
||||
domain_name: Optional[str] = Field(None, description="Active Directory domain or primary domain")
|
||||
m365_tenant_id: Optional[str] = Field(None, description="Microsoft 365 tenant ID (UUID format)")
|
||||
primary_contact: Optional[str] = Field(None, description="Primary contact person")
|
||||
notes: Optional[str] = Field(None, description="Additional notes about the client")
|
||||
is_active: Optional[bool] = Field(None, description="Whether client is currently active")
|
||||
|
||||
|
||||
class ClientResponse(ClientBase):
|
||||
"""Schema for Client responses with ID and timestamps."""
|
||||
|
||||
id: UUID = Field(..., description="Unique identifier for the client")
|
||||
created_at: datetime = Field(..., description="Timestamp when the client was created")
|
||||
updated_at: datetime = Field(..., description="Timestamp when the client was last updated")
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
54
api/schemas/context_snippet.py
Normal file
54
api/schemas/context_snippet.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Pydantic schemas for ContextSnippet model.
|
||||
|
||||
Request and response schemas for reusable context snippets.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ContextSnippetBase(BaseModel):
|
||||
"""Base schema with shared ContextSnippet fields."""
|
||||
|
||||
project_id: Optional[UUID] = Field(None, description="Project ID (optional)")
|
||||
client_id: Optional[UUID] = Field(None, description="Client ID (optional)")
|
||||
category: str = Field(..., description="Category: tech_decision, configuration, pattern, lesson_learned")
|
||||
title: str = Field(..., description="Brief title describing the snippet")
|
||||
dense_content: str = Field(..., description="Highly compressed information content")
|
||||
structured_data: Optional[str] = Field(None, description="JSON object for optional structured representation")
|
||||
tags: Optional[str] = Field(None, description="JSON array of tags for retrieval and categorization")
|
||||
relevance_score: float = Field(1.0, ge=0.0, le=10.0, description="Float score for ranking relevance (0.0-10.0)")
|
||||
usage_count: int = Field(0, ge=0, description="Integer count of how many times this snippet was retrieved")
|
||||
|
||||
|
||||
class ContextSnippetCreate(ContextSnippetBase):
|
||||
"""Schema for creating a new ContextSnippet."""
|
||||
pass
|
||||
|
||||
|
||||
class ContextSnippetUpdate(BaseModel):
|
||||
"""Schema for updating an existing ContextSnippet. All fields are optional."""
|
||||
|
||||
project_id: Optional[UUID] = Field(None, description="Project ID (optional)")
|
||||
client_id: Optional[UUID] = Field(None, description="Client ID (optional)")
|
||||
category: Optional[str] = Field(None, description="Category: tech_decision, configuration, pattern, lesson_learned")
|
||||
title: Optional[str] = Field(None, description="Brief title describing the snippet")
|
||||
dense_content: Optional[str] = Field(None, description="Highly compressed information content")
|
||||
structured_data: Optional[str] = Field(None, description="JSON object for optional structured representation")
|
||||
tags: Optional[str] = Field(None, description="JSON array of tags for retrieval and categorization")
|
||||
relevance_score: Optional[float] = Field(None, ge=0.0, le=10.0, description="Float score for ranking relevance (0.0-10.0)")
|
||||
usage_count: Optional[int] = Field(None, ge=0, description="Integer count of how many times this snippet was retrieved")
|
||||
|
||||
|
||||
class ContextSnippetResponse(ContextSnippetBase):
|
||||
"""Schema for ContextSnippet responses with ID and timestamps."""
|
||||
|
||||
id: UUID = Field(..., description="Unique identifier for the context snippet")
|
||||
created_at: datetime = Field(..., description="Timestamp when the snippet was created")
|
||||
updated_at: datetime = Field(..., description="Timestamp when the snippet was last updated")
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
56
api/schemas/conversation_context.py
Normal file
56
api/schemas/conversation_context.py
Normal file
@@ -0,0 +1,56 @@
|
||||
"""
|
||||
Pydantic schemas for ConversationContext model.
|
||||
|
||||
Request and response schemas for conversation context storage and recall.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ConversationContextBase(BaseModel):
|
||||
"""Base schema with shared ConversationContext fields."""
|
||||
|
||||
session_id: Optional[UUID] = Field(None, description="Session ID (optional)")
|
||||
project_id: Optional[UUID] = Field(None, description="Project ID (optional)")
|
||||
machine_id: Optional[UUID] = Field(None, description="Machine ID that created this context")
|
||||
context_type: str = Field(..., description="Type of context: session_summary, project_state, general_context")
|
||||
title: str = Field(..., description="Brief title describing the context")
|
||||
dense_summary: Optional[str] = Field(None, description="Compressed, structured summary (JSON or dense text)")
|
||||
key_decisions: Optional[str] = Field(None, description="JSON array of important decisions made")
|
||||
current_state: Optional[str] = Field(None, description="JSON object describing what's currently in progress")
|
||||
tags: Optional[str] = Field(None, description="JSON array of tags for retrieval and categorization")
|
||||
relevance_score: float = Field(1.0, ge=0.0, le=10.0, description="Float score for ranking relevance (0.0-10.0)")
|
||||
|
||||
|
||||
class ConversationContextCreate(ConversationContextBase):
|
||||
"""Schema for creating a new ConversationContext."""
|
||||
pass
|
||||
|
||||
|
||||
class ConversationContextUpdate(BaseModel):
|
||||
"""Schema for updating an existing ConversationContext. All fields are optional."""
|
||||
|
||||
session_id: Optional[UUID] = Field(None, description="Session ID (optional)")
|
||||
project_id: Optional[UUID] = Field(None, description="Project ID (optional)")
|
||||
machine_id: Optional[UUID] = Field(None, description="Machine ID that created this context")
|
||||
context_type: Optional[str] = Field(None, description="Type of context: session_summary, project_state, general_context")
|
||||
title: Optional[str] = Field(None, description="Brief title describing the context")
|
||||
dense_summary: Optional[str] = Field(None, description="Compressed, structured summary (JSON or dense text)")
|
||||
key_decisions: Optional[str] = Field(None, description="JSON array of important decisions made")
|
||||
current_state: Optional[str] = Field(None, description="JSON object describing what's currently in progress")
|
||||
tags: Optional[str] = Field(None, description="JSON array of tags for retrieval and categorization")
|
||||
relevance_score: Optional[float] = Field(None, ge=0.0, le=10.0, description="Float score for ranking relevance (0.0-10.0)")
|
||||
|
||||
|
||||
class ConversationContextResponse(ConversationContextBase):
|
||||
"""Schema for ConversationContext responses with ID and timestamps."""
|
||||
|
||||
id: UUID = Field(..., description="Unique identifier for the conversation context")
|
||||
created_at: datetime = Field(..., description="Timestamp when the context was created")
|
||||
updated_at: datetime = Field(..., description="Timestamp when the context was last updated")
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
176
api/schemas/credential.py
Normal file
176
api/schemas/credential.py
Normal file
@@ -0,0 +1,176 @@
|
||||
"""
|
||||
Pydantic schemas for Credential model.
|
||||
|
||||
Request and response schemas for secure credential storage.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
from api.utils.crypto import decrypt_string
|
||||
|
||||
|
||||
class CredentialBase(BaseModel):
|
||||
"""Base schema with shared Credential fields."""
|
||||
|
||||
client_id: Optional[UUID] = Field(None, description="Reference to client")
|
||||
service_id: Optional[UUID] = Field(None, description="Reference to service")
|
||||
infrastructure_id: Optional[UUID] = Field(None, description="Reference to infrastructure component")
|
||||
credential_type: str = Field(..., description="Type of credential: password, api_key, oauth, ssh_key, shared_secret, jwt, connection_string, certificate")
|
||||
service_name: str = Field(..., description="Display name for the service (e.g., 'Gitea Admin')")
|
||||
username: Optional[str] = Field(None, description="Username for authentication")
|
||||
client_id_oauth: Optional[str] = Field(None, description="OAuth client ID")
|
||||
tenant_id_oauth: Optional[str] = Field(None, description="OAuth tenant ID")
|
||||
public_key: Optional[str] = Field(None, description="SSH public key (not encrypted)")
|
||||
integration_code: Optional[str] = Field(None, description="Integration code for services like Autotask")
|
||||
external_url: Optional[str] = Field(None, description="External URL for the service")
|
||||
internal_url: Optional[str] = Field(None, description="Internal URL for the service")
|
||||
custom_port: Optional[int] = Field(None, description="Custom port number if applicable")
|
||||
role_description: Optional[str] = Field(None, description="Description of access level/role")
|
||||
requires_vpn: bool = Field(False, description="Whether VPN is required for access")
|
||||
requires_2fa: bool = Field(False, description="Whether 2FA is required")
|
||||
ssh_key_auth_enabled: bool = Field(False, description="Whether SSH key authentication is enabled")
|
||||
access_level: Optional[str] = Field(None, description="Description of access level")
|
||||
expires_at: Optional[datetime] = Field(None, description="When the credential expires")
|
||||
last_rotated_at: Optional[datetime] = Field(None, description="When the credential was last rotated")
|
||||
is_active: bool = Field(True, description="Whether the credential is currently active")
|
||||
|
||||
|
||||
class CredentialCreate(CredentialBase):
|
||||
"""Schema for creating a new Credential."""
|
||||
|
||||
password: Optional[str] = Field(None, description="Plain text password (will be encrypted before storage)")
|
||||
api_key: Optional[str] = Field(None, description="Plain text API key (will be encrypted before storage)")
|
||||
client_secret: Optional[str] = Field(None, description="Plain text OAuth client secret (will be encrypted before storage)")
|
||||
token: Optional[str] = Field(None, description="Plain text bearer/access token (will be encrypted before storage)")
|
||||
connection_string: Optional[str] = Field(None, description="Plain text connection string (will be encrypted before storage)")
|
||||
|
||||
|
||||
class CredentialUpdate(BaseModel):
|
||||
"""Schema for updating an existing Credential. All fields are optional."""
|
||||
|
||||
client_id: Optional[UUID] = Field(None, description="Reference to client")
|
||||
service_id: Optional[UUID] = Field(None, description="Reference to service")
|
||||
infrastructure_id: Optional[UUID] = Field(None, description="Reference to infrastructure component")
|
||||
credential_type: Optional[str] = Field(None, description="Type of credential")
|
||||
service_name: Optional[str] = Field(None, description="Display name for the service")
|
||||
username: Optional[str] = Field(None, description="Username for authentication")
|
||||
password: Optional[str] = Field(None, description="Plain text password (will be encrypted before storage)")
|
||||
api_key: Optional[str] = Field(None, description="Plain text API key (will be encrypted before storage)")
|
||||
client_id_oauth: Optional[str] = Field(None, description="OAuth client ID")
|
||||
client_secret: Optional[str] = Field(None, description="Plain text OAuth client secret (will be encrypted before storage)")
|
||||
tenant_id_oauth: Optional[str] = Field(None, description="OAuth tenant ID")
|
||||
public_key: Optional[str] = Field(None, description="SSH public key")
|
||||
token: Optional[str] = Field(None, description="Plain text bearer/access token (will be encrypted before storage)")
|
||||
connection_string: Optional[str] = Field(None, description="Plain text connection string (will be encrypted before storage)")
|
||||
integration_code: Optional[str] = Field(None, description="Integration code")
|
||||
external_url: Optional[str] = Field(None, description="External URL for the service")
|
||||
internal_url: Optional[str] = Field(None, description="Internal URL for the service")
|
||||
custom_port: Optional[int] = Field(None, description="Custom port number")
|
||||
role_description: Optional[str] = Field(None, description="Description of access level/role")
|
||||
requires_vpn: Optional[bool] = Field(None, description="Whether VPN is required")
|
||||
requires_2fa: Optional[bool] = Field(None, description="Whether 2FA is required")
|
||||
ssh_key_auth_enabled: Optional[bool] = Field(None, description="Whether SSH key authentication is enabled")
|
||||
access_level: Optional[str] = Field(None, description="Description of access level")
|
||||
expires_at: Optional[datetime] = Field(None, description="When the credential expires")
|
||||
last_rotated_at: Optional[datetime] = Field(None, description="When the credential was last rotated")
|
||||
is_active: Optional[bool] = Field(None, description="Whether the credential is active")
|
||||
|
||||
|
||||
class CredentialResponse(BaseModel):
|
||||
"""Schema for Credential responses with ID and timestamps. Includes decrypted values."""
|
||||
|
||||
id: UUID = Field(..., description="Unique identifier for the credential")
|
||||
client_id: Optional[UUID] = Field(None, description="Reference to client")
|
||||
service_id: Optional[UUID] = Field(None, description="Reference to service")
|
||||
infrastructure_id: Optional[UUID] = Field(None, description="Reference to infrastructure component")
|
||||
credential_type: str = Field(..., description="Type of credential")
|
||||
service_name: str = Field(..., description="Display name for the service")
|
||||
username: Optional[str] = Field(None, description="Username for authentication")
|
||||
|
||||
# Decrypted sensitive fields (computed from encrypted database fields)
|
||||
password: Optional[str] = Field(None, description="Decrypted password")
|
||||
api_key: Optional[str] = Field(None, description="Decrypted API key")
|
||||
client_secret: Optional[str] = Field(None, description="Decrypted OAuth client secret")
|
||||
token: Optional[str] = Field(None, description="Decrypted bearer/access token")
|
||||
connection_string: Optional[str] = Field(None, description="Decrypted connection string")
|
||||
|
||||
# OAuth and other non-encrypted fields
|
||||
client_id_oauth: Optional[str] = Field(None, description="OAuth client ID")
|
||||
tenant_id_oauth: Optional[str] = Field(None, description="OAuth tenant ID")
|
||||
public_key: Optional[str] = Field(None, description="SSH public key")
|
||||
integration_code: Optional[str] = Field(None, description="Integration code")
|
||||
external_url: Optional[str] = Field(None, description="External URL for the service")
|
||||
internal_url: Optional[str] = Field(None, description="Internal URL for the service")
|
||||
custom_port: Optional[int] = Field(None, description="Custom port number")
|
||||
role_description: Optional[str] = Field(None, description="Description of access level/role")
|
||||
requires_vpn: bool = Field(..., description="Whether VPN is required")
|
||||
requires_2fa: bool = Field(..., description="Whether 2FA is required")
|
||||
ssh_key_auth_enabled: bool = Field(..., description="Whether SSH key authentication is enabled")
|
||||
access_level: Optional[str] = Field(None, description="Description of access level")
|
||||
expires_at: Optional[datetime] = Field(None, description="When the credential expires")
|
||||
last_rotated_at: Optional[datetime] = Field(None, description="When the credential was last rotated")
|
||||
is_active: bool = Field(..., description="Whether the credential is active")
|
||||
created_at: datetime = Field(..., description="Timestamp when the credential was created")
|
||||
updated_at: datetime = Field(..., description="Timestamp when the credential was last updated")
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
@field_validator("password", mode="before")
|
||||
@classmethod
|
||||
def decrypt_password(cls, v):
|
||||
"""Decrypt password_encrypted field from database."""
|
||||
if v is None:
|
||||
return None
|
||||
if isinstance(v, bytes):
|
||||
# This is the encrypted bytes from password_encrypted field
|
||||
encrypted_str = v.decode('utf-8')
|
||||
return decrypt_string(encrypted_str, default=None)
|
||||
return v
|
||||
|
||||
@field_validator("api_key", mode="before")
|
||||
@classmethod
|
||||
def decrypt_api_key(cls, v):
|
||||
"""Decrypt api_key_encrypted field from database."""
|
||||
if v is None:
|
||||
return None
|
||||
if isinstance(v, bytes):
|
||||
encrypted_str = v.decode('utf-8')
|
||||
return decrypt_string(encrypted_str, default=None)
|
||||
return v
|
||||
|
||||
@field_validator("client_secret", mode="before")
|
||||
@classmethod
|
||||
def decrypt_client_secret(cls, v):
|
||||
"""Decrypt client_secret_encrypted field from database."""
|
||||
if v is None:
|
||||
return None
|
||||
if isinstance(v, bytes):
|
||||
encrypted_str = v.decode('utf-8')
|
||||
return decrypt_string(encrypted_str, default=None)
|
||||
return v
|
||||
|
||||
@field_validator("token", mode="before")
|
||||
@classmethod
|
||||
def decrypt_token(cls, v):
|
||||
"""Decrypt token_encrypted field from database."""
|
||||
if v is None:
|
||||
return None
|
||||
if isinstance(v, bytes):
|
||||
encrypted_str = v.decode('utf-8')
|
||||
return decrypt_string(encrypted_str, default=None)
|
||||
return v
|
||||
|
||||
@field_validator("connection_string", mode="before")
|
||||
@classmethod
|
||||
def decrypt_connection_string(cls, v):
|
||||
"""Decrypt connection_string_encrypted field from database."""
|
||||
if v is None:
|
||||
return None
|
||||
if isinstance(v, bytes):
|
||||
encrypted_str = v.decode('utf-8')
|
||||
return decrypt_string(encrypted_str, default=None)
|
||||
return v
|
||||
47
api/schemas/credential_audit_log.py
Normal file
47
api/schemas/credential_audit_log.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""
|
||||
Pydantic schemas for CredentialAuditLog model.
|
||||
|
||||
Request and response schemas for credential audit logging.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class CredentialAuditLogBase(BaseModel):
|
||||
"""Base schema with shared CredentialAuditLog fields."""
|
||||
|
||||
credential_id: UUID = Field(..., description="Reference to the credential")
|
||||
action: str = Field(..., description="Type of action: view, create, update, delete, rotate, decrypt")
|
||||
user_id: str = Field(..., description="User who performed the action (JWT sub claim)")
|
||||
ip_address: Optional[str] = Field(None, description="IP address (IPv4 or IPv6)")
|
||||
user_agent: Optional[str] = Field(None, description="Browser/client user agent string")
|
||||
details: Optional[str] = Field(None, description="JSON string with additional context (what changed, why, etc.)")
|
||||
|
||||
|
||||
class CredentialAuditLogCreate(CredentialAuditLogBase):
|
||||
"""Schema for creating a new CredentialAuditLog entry."""
|
||||
pass
|
||||
|
||||
|
||||
class CredentialAuditLogUpdate(BaseModel):
|
||||
"""
|
||||
Schema for updating an existing CredentialAuditLog.
|
||||
|
||||
NOTE: Audit logs should be immutable in most cases. This schema is provided
|
||||
for completeness but should rarely be used.
|
||||
"""
|
||||
|
||||
details: Optional[str] = Field(None, description="JSON string with additional context")
|
||||
|
||||
|
||||
class CredentialAuditLogResponse(CredentialAuditLogBase):
|
||||
"""Schema for CredentialAuditLog responses with ID and timestamp."""
|
||||
|
||||
id: UUID = Field(..., description="Unique identifier for the audit log entry")
|
||||
timestamp: datetime = Field(..., description="When the action was performed")
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
52
api/schemas/decision_log.py
Normal file
52
api/schemas/decision_log.py
Normal file
@@ -0,0 +1,52 @@
|
||||
"""
|
||||
Pydantic schemas for DecisionLog model.
|
||||
|
||||
Request and response schemas for tracking important decisions made during work.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class DecisionLogBase(BaseModel):
|
||||
"""Base schema with shared DecisionLog fields."""
|
||||
|
||||
project_id: Optional[UUID] = Field(None, description="Project ID (optional)")
|
||||
session_id: Optional[UUID] = Field(None, description="Session ID (optional)")
|
||||
decision_type: str = Field(..., description="Type of decision: technical, architectural, process, security")
|
||||
decision_text: str = Field(..., description="What was decided (the actual decision)")
|
||||
rationale: Optional[str] = Field(None, description="Why this decision was made")
|
||||
alternatives_considered: Optional[str] = Field(None, description="JSON array of other options that were considered")
|
||||
impact: str = Field("medium", description="Impact level: low, medium, high, critical")
|
||||
tags: Optional[str] = Field(None, description="JSON array of tags for retrieval and categorization")
|
||||
|
||||
|
||||
class DecisionLogCreate(DecisionLogBase):
|
||||
"""Schema for creating a new DecisionLog."""
|
||||
pass
|
||||
|
||||
|
||||
class DecisionLogUpdate(BaseModel):
|
||||
"""Schema for updating an existing DecisionLog. All fields are optional."""
|
||||
|
||||
project_id: Optional[UUID] = Field(None, description="Project ID (optional)")
|
||||
session_id: Optional[UUID] = Field(None, description="Session ID (optional)")
|
||||
decision_type: Optional[str] = Field(None, description="Type of decision: technical, architectural, process, security")
|
||||
decision_text: Optional[str] = Field(None, description="What was decided (the actual decision)")
|
||||
rationale: Optional[str] = Field(None, description="Why this decision was made")
|
||||
alternatives_considered: Optional[str] = Field(None, description="JSON array of other options that were considered")
|
||||
impact: Optional[str] = Field(None, description="Impact level: low, medium, high, critical")
|
||||
tags: Optional[str] = Field(None, description="JSON array of tags for retrieval and categorization")
|
||||
|
||||
|
||||
class DecisionLogResponse(DecisionLogBase):
|
||||
"""Schema for DecisionLog responses with ID and timestamps."""
|
||||
|
||||
id: UUID = Field(..., description="Unique identifier for the decision log")
|
||||
created_at: datetime = Field(..., description="Timestamp when the decision was logged")
|
||||
updated_at: datetime = Field(..., description="Timestamp when the decision log was last updated")
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
56
api/schemas/firewall_rule.py
Normal file
56
api/schemas/firewall_rule.py
Normal file
@@ -0,0 +1,56 @@
|
||||
"""
|
||||
Pydantic schemas for FirewallRule model.
|
||||
|
||||
Request and response schemas for network security rules.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class FirewallRuleBase(BaseModel):
|
||||
"""Base schema with shared FirewallRule fields."""
|
||||
|
||||
infrastructure_id: Optional[UUID] = Field(None, description="Reference to the infrastructure this rule applies to")
|
||||
rule_name: Optional[str] = Field(None, description="Name of the firewall rule")
|
||||
source_cidr: Optional[str] = Field(None, description="Source CIDR notation")
|
||||
destination_cidr: Optional[str] = Field(None, description="Destination CIDR notation")
|
||||
port: Optional[int] = Field(None, description="Port number")
|
||||
protocol: Optional[str] = Field(None, description="Protocol: tcp, udp, icmp")
|
||||
action: Optional[str] = Field(None, description="Action: allow, deny, drop")
|
||||
rule_order: Optional[int] = Field(None, description="Order of the rule in the firewall")
|
||||
notes: Optional[str] = Field(None, description="Additional notes")
|
||||
created_by: Optional[str] = Field(None, description="Who created the rule")
|
||||
|
||||
|
||||
class FirewallRuleCreate(FirewallRuleBase):
|
||||
"""Schema for creating a new FirewallRule."""
|
||||
pass
|
||||
|
||||
|
||||
class FirewallRuleUpdate(BaseModel):
|
||||
"""Schema for updating an existing FirewallRule. All fields are optional."""
|
||||
|
||||
infrastructure_id: Optional[UUID] = Field(None, description="Reference to the infrastructure this rule applies to")
|
||||
rule_name: Optional[str] = Field(None, description="Name of the firewall rule")
|
||||
source_cidr: Optional[str] = Field(None, description="Source CIDR notation")
|
||||
destination_cidr: Optional[str] = Field(None, description="Destination CIDR notation")
|
||||
port: Optional[int] = Field(None, description="Port number")
|
||||
protocol: Optional[str] = Field(None, description="Protocol: tcp, udp, icmp")
|
||||
action: Optional[str] = Field(None, description="Action: allow, deny, drop")
|
||||
rule_order: Optional[int] = Field(None, description="Order of the rule in the firewall")
|
||||
notes: Optional[str] = Field(None, description="Additional notes")
|
||||
created_by: Optional[str] = Field(None, description="Who created the rule")
|
||||
|
||||
|
||||
class FirewallRuleResponse(FirewallRuleBase):
|
||||
"""Schema for FirewallRule responses with ID and timestamps."""
|
||||
|
||||
id: UUID = Field(..., description="Unique identifier for the firewall rule")
|
||||
created_at: datetime = Field(..., description="Timestamp when the firewall rule was created")
|
||||
updated_at: datetime = Field(..., description="Timestamp when the firewall rule was last updated")
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
73
api/schemas/infrastructure.py
Normal file
73
api/schemas/infrastructure.py
Normal file
@@ -0,0 +1,73 @@
|
||||
"""
|
||||
Pydantic schemas for Infrastructure model.
|
||||
|
||||
Request and response schemas for infrastructure assets including servers,
|
||||
network devices, workstations, and other IT infrastructure.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class InfrastructureBase(BaseModel):
|
||||
"""Base schema with shared Infrastructure fields."""
|
||||
|
||||
client_id: Optional[str] = Field(None, description="Reference to the client")
|
||||
site_id: Optional[str] = Field(None, description="Reference to the site this infrastructure is located at")
|
||||
asset_type: str = Field(..., description="Type: physical_server, virtual_machine, container, network_device, nas_storage, workstation, firewall, domain_controller")
|
||||
hostname: str = Field(..., description="Hostname of the infrastructure")
|
||||
ip_address: Optional[str] = Field(None, description="IP address (IPv4 or IPv6)")
|
||||
mac_address: Optional[str] = Field(None, description="MAC address")
|
||||
os: Optional[str] = Field(None, description="Operating system name (e.g., 'Ubuntu 22.04', 'Windows Server 2022')")
|
||||
os_version: Optional[str] = Field(None, description="Operating system version (e.g., '6.22', '2008 R2', '22.04')")
|
||||
role_description: Optional[str] = Field(None, description="Description of the infrastructure's role")
|
||||
parent_host_id: Optional[str] = Field(None, description="Reference to parent host for VMs/containers")
|
||||
status: str = Field("active", description="Status: active, migration_source, migration_destination, decommissioned")
|
||||
environmental_notes: Optional[str] = Field(None, description="Special environmental constraints or notes")
|
||||
powershell_version: Optional[str] = Field(None, description="PowerShell version (e.g., '2.0', '5.1', '7.4')")
|
||||
shell_type: Optional[str] = Field(None, description="Shell type: bash, cmd, powershell, sh")
|
||||
package_manager: Optional[str] = Field(None, description="Package manager: apt, yum, chocolatey, none")
|
||||
has_gui: bool = Field(True, description="Whether the system has a GUI")
|
||||
limitations: Optional[str] = Field(None, description='JSON array of limitations (e.g., ["no_ps7", "smb1_only", "dos_6.22_commands"])')
|
||||
notes: Optional[str] = Field(None, description="Additional notes")
|
||||
|
||||
|
||||
class InfrastructureCreate(InfrastructureBase):
|
||||
"""Schema for creating a new Infrastructure item."""
|
||||
pass
|
||||
|
||||
|
||||
class InfrastructureUpdate(BaseModel):
|
||||
"""Schema for updating an existing Infrastructure item. All fields are optional."""
|
||||
|
||||
client_id: Optional[str] = Field(None, description="Reference to the client")
|
||||
site_id: Optional[str] = Field(None, description="Reference to the site this infrastructure is located at")
|
||||
asset_type: Optional[str] = Field(None, description="Type: physical_server, virtual_machine, container, network_device, nas_storage, workstation, firewall, domain_controller")
|
||||
hostname: Optional[str] = Field(None, description="Hostname of the infrastructure")
|
||||
ip_address: Optional[str] = Field(None, description="IP address (IPv4 or IPv6)")
|
||||
mac_address: Optional[str] = Field(None, description="MAC address")
|
||||
os: Optional[str] = Field(None, description="Operating system name (e.g., 'Ubuntu 22.04', 'Windows Server 2022')")
|
||||
os_version: Optional[str] = Field(None, description="Operating system version (e.g., '6.22', '2008 R2', '22.04')")
|
||||
role_description: Optional[str] = Field(None, description="Description of the infrastructure's role")
|
||||
parent_host_id: Optional[str] = Field(None, description="Reference to parent host for VMs/containers")
|
||||
status: Optional[str] = Field(None, description="Status: active, migration_source, migration_destination, decommissioned")
|
||||
environmental_notes: Optional[str] = Field(None, description="Special environmental constraints or notes")
|
||||
powershell_version: Optional[str] = Field(None, description="PowerShell version (e.g., '2.0', '5.1', '7.4')")
|
||||
shell_type: Optional[str] = Field(None, description="Shell type: bash, cmd, powershell, sh")
|
||||
package_manager: Optional[str] = Field(None, description="Package manager: apt, yum, chocolatey, none")
|
||||
has_gui: Optional[bool] = Field(None, description="Whether the system has a GUI")
|
||||
limitations: Optional[str] = Field(None, description='JSON array of limitations (e.g., ["no_ps7", "smb1_only", "dos_6.22_commands"])')
|
||||
notes: Optional[str] = Field(None, description="Additional notes")
|
||||
|
||||
|
||||
class InfrastructureResponse(InfrastructureBase):
|
||||
"""Schema for Infrastructure responses with ID and timestamps."""
|
||||
|
||||
id: UUID = Field(..., description="Unique identifier for the infrastructure item")
|
||||
created_at: datetime = Field(..., description="Timestamp when the infrastructure was created")
|
||||
updated_at: datetime = Field(..., description="Timestamp when the infrastructure was last updated")
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
50
api/schemas/m365_tenant.py
Normal file
50
api/schemas/m365_tenant.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""
|
||||
Pydantic schemas for M365Tenant model.
|
||||
|
||||
Request and response schemas for Microsoft 365 tenant configurations.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class M365TenantBase(BaseModel):
|
||||
"""Base schema with shared M365Tenant fields."""
|
||||
|
||||
client_id: Optional[UUID] = Field(None, description="Reference to the client")
|
||||
tenant_id: str = Field(..., description="Microsoft tenant ID (UUID)")
|
||||
tenant_name: Optional[str] = Field(None, description="Tenant name (e.g., 'dataforth.com')")
|
||||
default_domain: Optional[str] = Field(None, description="Default domain (e.g., 'dataforthcorp.onmicrosoft.com')")
|
||||
admin_email: Optional[str] = Field(None, description="Administrator email address")
|
||||
cipp_name: Optional[str] = Field(None, description="Name in CIPP portal")
|
||||
notes: Optional[str] = Field(None, description="Additional notes")
|
||||
|
||||
|
||||
class M365TenantCreate(M365TenantBase):
|
||||
"""Schema for creating a new M365Tenant."""
|
||||
pass
|
||||
|
||||
|
||||
class M365TenantUpdate(BaseModel):
|
||||
"""Schema for updating an existing M365Tenant. All fields are optional."""
|
||||
|
||||
client_id: Optional[UUID] = Field(None, description="Reference to the client")
|
||||
tenant_id: Optional[str] = Field(None, description="Microsoft tenant ID (UUID)")
|
||||
tenant_name: Optional[str] = Field(None, description="Tenant name (e.g., 'dataforth.com')")
|
||||
default_domain: Optional[str] = Field(None, description="Default domain (e.g., 'dataforthcorp.onmicrosoft.com')")
|
||||
admin_email: Optional[str] = Field(None, description="Administrator email address")
|
||||
cipp_name: Optional[str] = Field(None, description="Name in CIPP portal")
|
||||
notes: Optional[str] = Field(None, description="Additional notes")
|
||||
|
||||
|
||||
class M365TenantResponse(M365TenantBase):
|
||||
"""Schema for M365Tenant responses with ID and timestamps."""
|
||||
|
||||
id: UUID = Field(..., description="Unique identifier for the M365 tenant")
|
||||
created_at: datetime = Field(..., description="Timestamp when the M365 tenant was created")
|
||||
updated_at: datetime = Field(..., description="Timestamp when the M365 tenant was last updated")
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
98
api/schemas/machine.py
Normal file
98
api/schemas/machine.py
Normal file
@@ -0,0 +1,98 @@
|
||||
"""
|
||||
Pydantic schemas for Machine model.
|
||||
|
||||
Request and response schemas for technician's machines used for MSP work.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class MachineBase(BaseModel):
|
||||
"""Base schema with shared Machine fields."""
|
||||
|
||||
hostname: str = Field(..., description="Machine hostname from `hostname` command")
|
||||
machine_fingerprint: Optional[str] = Field(None, description="SHA256 hash: hostname + username + platform + home_directory")
|
||||
friendly_name: Optional[str] = Field(None, description="Human-readable name like 'Main Laptop' or 'Home Desktop'")
|
||||
machine_type: Optional[str] = Field(None, description="Type of machine: laptop, desktop, workstation, vm")
|
||||
platform: Optional[str] = Field(None, description="Operating system platform: win32, darwin, linux")
|
||||
os_version: Optional[str] = Field(None, description="Operating system version")
|
||||
username: Optional[str] = Field(None, description="Username from `whoami` command")
|
||||
home_directory: Optional[str] = Field(None, description="User home directory path")
|
||||
has_vpn_access: bool = Field(False, description="Whether machine can connect to client networks")
|
||||
vpn_profiles: Optional[str] = Field(None, description="JSON array of available VPN profiles")
|
||||
has_docker: bool = Field(False, description="Whether Docker is installed")
|
||||
has_powershell: bool = Field(False, description="Whether PowerShell is installed")
|
||||
powershell_version: Optional[str] = Field(None, description="PowerShell version if installed")
|
||||
has_ssh: bool = Field(True, description="Whether SSH is available")
|
||||
has_git: bool = Field(True, description="Whether Git is installed")
|
||||
typical_network_location: Optional[str] = Field(None, description="Typical network location: home, office, mobile")
|
||||
static_ip: Optional[str] = Field(None, description="Static IP address if applicable (supports IPv4/IPv6)")
|
||||
claude_working_directory: Optional[str] = Field(None, description="Primary working directory for Claude Code")
|
||||
additional_working_dirs: Optional[str] = Field(None, description="JSON array of additional working directories")
|
||||
installed_tools: Optional[str] = Field(None, description='JSON object with tool versions like {"git": "2.40", "docker": "24.0"}')
|
||||
available_mcps: Optional[str] = Field(None, description="JSON array of available MCP servers")
|
||||
mcp_capabilities: Optional[str] = Field(None, description="JSON object with MCP capabilities")
|
||||
available_skills: Optional[str] = Field(None, description="JSON array of available skills")
|
||||
skill_paths: Optional[str] = Field(None, description="JSON object mapping skill names to paths")
|
||||
preferred_shell: Optional[str] = Field(None, description="Preferred shell: powershell, bash, zsh, cmd")
|
||||
package_manager_commands: Optional[str] = Field(None, description="JSON object with package manager commands")
|
||||
is_primary: bool = Field(False, description="Whether this is the primary machine")
|
||||
is_active: bool = Field(True, description="Whether machine is currently active")
|
||||
last_seen: Optional[datetime] = Field(None, description="Last time machine was seen")
|
||||
last_session_id: Optional[str] = Field(None, description="UUID of last session from this machine")
|
||||
notes: Optional[str] = Field(None, description="Additional notes about the machine")
|
||||
|
||||
|
||||
class MachineCreate(MachineBase):
|
||||
"""Schema for creating a new Machine."""
|
||||
pass
|
||||
|
||||
|
||||
class MachineUpdate(BaseModel):
|
||||
"""Schema for updating an existing Machine. All fields are optional."""
|
||||
|
||||
hostname: Optional[str] = Field(None, description="Machine hostname from `hostname` command")
|
||||
machine_fingerprint: Optional[str] = Field(None, description="SHA256 hash: hostname + username + platform + home_directory")
|
||||
friendly_name: Optional[str] = Field(None, description="Human-readable name like 'Main Laptop' or 'Home Desktop'")
|
||||
machine_type: Optional[str] = Field(None, description="Type of machine: laptop, desktop, workstation, vm")
|
||||
platform: Optional[str] = Field(None, description="Operating system platform: win32, darwin, linux")
|
||||
os_version: Optional[str] = Field(None, description="Operating system version")
|
||||
username: Optional[str] = Field(None, description="Username from `whoami` command")
|
||||
home_directory: Optional[str] = Field(None, description="User home directory path")
|
||||
has_vpn_access: Optional[bool] = Field(None, description="Whether machine can connect to client networks")
|
||||
vpn_profiles: Optional[str] = Field(None, description="JSON array of available VPN profiles")
|
||||
has_docker: Optional[bool] = Field(None, description="Whether Docker is installed")
|
||||
has_powershell: Optional[bool] = Field(None, description="Whether PowerShell is installed")
|
||||
powershell_version: Optional[str] = Field(None, description="PowerShell version if installed")
|
||||
has_ssh: Optional[bool] = Field(None, description="Whether SSH is available")
|
||||
has_git: Optional[bool] = Field(None, description="Whether Git is installed")
|
||||
typical_network_location: Optional[str] = Field(None, description="Typical network location: home, office, mobile")
|
||||
static_ip: Optional[str] = Field(None, description="Static IP address if applicable (supports IPv4/IPv6)")
|
||||
claude_working_directory: Optional[str] = Field(None, description="Primary working directory for Claude Code")
|
||||
additional_working_dirs: Optional[str] = Field(None, description="JSON array of additional working directories")
|
||||
installed_tools: Optional[str] = Field(None, description='JSON object with tool versions like {"git": "2.40", "docker": "24.0"}')
|
||||
available_mcps: Optional[str] = Field(None, description="JSON array of available MCP servers")
|
||||
mcp_capabilities: Optional[str] = Field(None, description="JSON object with MCP capabilities")
|
||||
available_skills: Optional[str] = Field(None, description="JSON array of available skills")
|
||||
skill_paths: Optional[str] = Field(None, description="JSON object mapping skill names to paths")
|
||||
preferred_shell: Optional[str] = Field(None, description="Preferred shell: powershell, bash, zsh, cmd")
|
||||
package_manager_commands: Optional[str] = Field(None, description="JSON object with package manager commands")
|
||||
is_primary: Optional[bool] = Field(None, description="Whether this is the primary machine")
|
||||
is_active: Optional[bool] = Field(None, description="Whether machine is currently active")
|
||||
last_seen: Optional[datetime] = Field(None, description="Last time machine was seen")
|
||||
last_session_id: Optional[str] = Field(None, description="UUID of last session from this machine")
|
||||
notes: Optional[str] = Field(None, description="Additional notes about the machine")
|
||||
|
||||
|
||||
class MachineResponse(MachineBase):
|
||||
"""Schema for Machine responses with ID and timestamps."""
|
||||
|
||||
id: UUID = Field(..., description="Unique identifier for the machine")
|
||||
created_at: datetime = Field(..., description="Timestamp when the machine was created")
|
||||
updated_at: datetime = Field(..., description="Timestamp when the machine was last updated")
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
52
api/schemas/network.py
Normal file
52
api/schemas/network.py
Normal file
@@ -0,0 +1,52 @@
|
||||
"""
|
||||
Pydantic schemas for Network model.
|
||||
|
||||
Request and response schemas for network segments and VLANs.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class NetworkBase(BaseModel):
|
||||
"""Base schema with shared Network fields."""
|
||||
|
||||
client_id: Optional[UUID] = Field(None, description="Reference to the client")
|
||||
site_id: Optional[UUID] = Field(None, description="Reference to the site")
|
||||
network_name: str = Field(..., description="Name of the network")
|
||||
network_type: Optional[str] = Field(None, description="Type: lan, vpn, vlan, isolated, dmz")
|
||||
cidr: str = Field(..., description="Network CIDR notation (e.g., '192.168.0.0/24')")
|
||||
gateway_ip: Optional[str] = Field(None, description="Gateway IP address")
|
||||
vlan_id: Optional[int] = Field(None, description="VLAN ID if applicable")
|
||||
notes: Optional[str] = Field(None, description="Additional notes")
|
||||
|
||||
|
||||
class NetworkCreate(NetworkBase):
|
||||
"""Schema for creating a new Network."""
|
||||
pass
|
||||
|
||||
|
||||
class NetworkUpdate(BaseModel):
|
||||
"""Schema for updating an existing Network. All fields are optional."""
|
||||
|
||||
client_id: Optional[UUID] = Field(None, description="Reference to the client")
|
||||
site_id: Optional[UUID] = Field(None, description="Reference to the site")
|
||||
network_name: Optional[str] = Field(None, description="Name of the network")
|
||||
network_type: Optional[str] = Field(None, description="Type: lan, vpn, vlan, isolated, dmz")
|
||||
cidr: Optional[str] = Field(None, description="Network CIDR notation (e.g., '192.168.0.0/24')")
|
||||
gateway_ip: Optional[str] = Field(None, description="Gateway IP address")
|
||||
vlan_id: Optional[int] = Field(None, description="VLAN ID if applicable")
|
||||
notes: Optional[str] = Field(None, description="Additional notes")
|
||||
|
||||
|
||||
class NetworkResponse(NetworkBase):
|
||||
"""Schema for Network responses with ID and timestamps."""
|
||||
|
||||
id: UUID = Field(..., description="Unique identifier for the network")
|
||||
created_at: datetime = Field(..., description="Timestamp when the network was created")
|
||||
updated_at: datetime = Field(..., description="Timestamp when the network was last updated")
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
64
api/schemas/project.py
Normal file
64
api/schemas/project.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""
|
||||
Pydantic schemas for Project model.
|
||||
|
||||
Request and response schemas for individual projects and engagements.
|
||||
"""
|
||||
|
||||
from datetime import date, datetime
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ProjectBase(BaseModel):
|
||||
"""Base schema with shared Project fields."""
|
||||
|
||||
client_id: str = Field(..., description="Foreign key to clients table (UUID)")
|
||||
name: str = Field(..., description="Project name")
|
||||
slug: Optional[str] = Field(None, description="URL-safe slug (directory name like 'dataforth-dos')")
|
||||
category: Optional[str] = Field(None, description="Project category: client_project, internal_product, infrastructure, website, development_tool, documentation")
|
||||
status: str = Field("working", description="Status: complete, working, blocked, pending, critical, deferred")
|
||||
priority: Optional[str] = Field(None, description="Priority level: critical, high, medium, low")
|
||||
description: Optional[str] = Field(None, description="Project description")
|
||||
started_date: Optional[date] = Field(None, description="Date project started")
|
||||
target_completion_date: Optional[date] = Field(None, description="Target completion date")
|
||||
completed_date: Optional[date] = Field(None, description="Actual completion date")
|
||||
estimated_hours: Optional[float] = Field(None, description="Estimated hours for completion")
|
||||
actual_hours: Optional[float] = Field(None, description="Actual hours spent")
|
||||
gitea_repo_url: Optional[str] = Field(None, description="Gitea repository URL if applicable")
|
||||
notes: Optional[str] = Field(None, description="Additional notes about the project")
|
||||
|
||||
|
||||
class ProjectCreate(ProjectBase):
|
||||
"""Schema for creating a new Project."""
|
||||
pass
|
||||
|
||||
|
||||
class ProjectUpdate(BaseModel):
|
||||
"""Schema for updating an existing Project. All fields are optional."""
|
||||
|
||||
client_id: Optional[str] = Field(None, description="Foreign key to clients table (UUID)")
|
||||
name: Optional[str] = Field(None, description="Project name")
|
||||
slug: Optional[str] = Field(None, description="URL-safe slug (directory name like 'dataforth-dos')")
|
||||
category: Optional[str] = Field(None, description="Project category: client_project, internal_product, infrastructure, website, development_tool, documentation")
|
||||
status: Optional[str] = Field(None, description="Status: complete, working, blocked, pending, critical, deferred")
|
||||
priority: Optional[str] = Field(None, description="Priority level: critical, high, medium, low")
|
||||
description: Optional[str] = Field(None, description="Project description")
|
||||
started_date: Optional[date] = Field(None, description="Date project started")
|
||||
target_completion_date: Optional[date] = Field(None, description="Target completion date")
|
||||
completed_date: Optional[date] = Field(None, description="Actual completion date")
|
||||
estimated_hours: Optional[float] = Field(None, description="Estimated hours for completion")
|
||||
actual_hours: Optional[float] = Field(None, description="Actual hours spent")
|
||||
gitea_repo_url: Optional[str] = Field(None, description="Gitea repository URL if applicable")
|
||||
notes: Optional[str] = Field(None, description="Additional notes about the project")
|
||||
|
||||
|
||||
class ProjectResponse(ProjectBase):
|
||||
"""Schema for Project responses with ID and timestamps."""
|
||||
|
||||
id: UUID = Field(..., description="Unique identifier for the project")
|
||||
created_at: datetime = Field(..., description="Timestamp when the project was created")
|
||||
updated_at: datetime = Field(..., description="Timestamp when the project was last updated")
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
53
api/schemas/project_state.py
Normal file
53
api/schemas/project_state.py
Normal file
@@ -0,0 +1,53 @@
|
||||
"""
|
||||
Pydantic schemas for ProjectState model.
|
||||
|
||||
Request and response schemas for tracking current state of projects.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ProjectStateBase(BaseModel):
|
||||
"""Base schema with shared ProjectState fields."""
|
||||
|
||||
project_id: UUID = Field(..., description="Project ID (required, unique - one state per project)")
|
||||
last_session_id: Optional[UUID] = Field(None, description="Last session ID that updated this state")
|
||||
current_phase: Optional[str] = Field(None, description="Current phase or stage of the project")
|
||||
progress_percentage: int = Field(0, ge=0, le=100, description="Integer percentage of completion (0-100)")
|
||||
blockers: Optional[str] = Field(None, description="JSON array of current blockers preventing progress")
|
||||
next_actions: Optional[str] = Field(None, description="JSON array of next steps to take")
|
||||
context_summary: Optional[str] = Field(None, description="Dense overview text of where the project currently stands")
|
||||
key_files: Optional[str] = Field(None, description="JSON array of important file paths for this project")
|
||||
important_decisions: Optional[str] = Field(None, description="JSON array of key decisions made for this project")
|
||||
|
||||
|
||||
class ProjectStateCreate(ProjectStateBase):
|
||||
"""Schema for creating a new ProjectState."""
|
||||
pass
|
||||
|
||||
|
||||
class ProjectStateUpdate(BaseModel):
|
||||
"""Schema for updating an existing ProjectState. All fields are optional except project_id."""
|
||||
|
||||
last_session_id: Optional[UUID] = Field(None, description="Last session ID that updated this state")
|
||||
current_phase: Optional[str] = Field(None, description="Current phase or stage of the project")
|
||||
progress_percentage: Optional[int] = Field(None, ge=0, le=100, description="Integer percentage of completion (0-100)")
|
||||
blockers: Optional[str] = Field(None, description="JSON array of current blockers preventing progress")
|
||||
next_actions: Optional[str] = Field(None, description="JSON array of next steps to take")
|
||||
context_summary: Optional[str] = Field(None, description="Dense overview text of where the project currently stands")
|
||||
key_files: Optional[str] = Field(None, description="JSON array of important file paths for this project")
|
||||
important_decisions: Optional[str] = Field(None, description="JSON array of key decisions made for this project")
|
||||
|
||||
|
||||
class ProjectStateResponse(ProjectStateBase):
|
||||
"""Schema for ProjectState responses with ID and timestamps."""
|
||||
|
||||
id: UUID = Field(..., description="Unique identifier for the project state")
|
||||
created_at: datetime = Field(..., description="Timestamp when the state was created")
|
||||
updated_at: datetime = Field(..., description="Timestamp when the state was last updated")
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
60
api/schemas/security_incident.py
Normal file
60
api/schemas/security_incident.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""
|
||||
Pydantic schemas for SecurityIncident model.
|
||||
|
||||
Request and response schemas for security incident tracking.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class SecurityIncidentBase(BaseModel):
|
||||
"""Base schema with shared SecurityIncident fields."""
|
||||
|
||||
client_id: Optional[UUID] = Field(None, description="Reference to affected client")
|
||||
service_id: Optional[UUID] = Field(None, description="Reference to affected service")
|
||||
infrastructure_id: Optional[UUID] = Field(None, description="Reference to affected infrastructure")
|
||||
incident_type: Optional[str] = Field(None, description="Type of incident: bec, backdoor, malware, unauthorized_access, data_breach, phishing, ransomware, brute_force")
|
||||
incident_date: datetime = Field(..., description="When the incident occurred")
|
||||
severity: Optional[str] = Field(None, description="Severity level: critical, high, medium, low")
|
||||
description: str = Field(..., description="Detailed description of the incident")
|
||||
findings: Optional[str] = Field(None, description="Investigation results and findings")
|
||||
remediation_steps: Optional[str] = Field(None, description="Steps taken to remediate the incident")
|
||||
status: str = Field("investigating", description="Status: investigating, contained, resolved, monitoring")
|
||||
resolved_at: Optional[datetime] = Field(None, description="When the incident was resolved")
|
||||
notes: Optional[str] = Field(None, description="Additional notes and context")
|
||||
|
||||
|
||||
class SecurityIncidentCreate(SecurityIncidentBase):
|
||||
"""Schema for creating a new SecurityIncident."""
|
||||
pass
|
||||
|
||||
|
||||
class SecurityIncidentUpdate(BaseModel):
|
||||
"""Schema for updating an existing SecurityIncident. All fields are optional."""
|
||||
|
||||
client_id: Optional[UUID] = Field(None, description="Reference to affected client")
|
||||
service_id: Optional[UUID] = Field(None, description="Reference to affected service")
|
||||
infrastructure_id: Optional[UUID] = Field(None, description="Reference to affected infrastructure")
|
||||
incident_type: Optional[str] = Field(None, description="Type of incident")
|
||||
incident_date: Optional[datetime] = Field(None, description="When the incident occurred")
|
||||
severity: Optional[str] = Field(None, description="Severity level")
|
||||
description: Optional[str] = Field(None, description="Detailed description of the incident")
|
||||
findings: Optional[str] = Field(None, description="Investigation results and findings")
|
||||
remediation_steps: Optional[str] = Field(None, description="Steps taken to remediate the incident")
|
||||
status: Optional[str] = Field(None, description="Status of incident handling")
|
||||
resolved_at: Optional[datetime] = Field(None, description="When the incident was resolved")
|
||||
notes: Optional[str] = Field(None, description="Additional notes and context")
|
||||
|
||||
|
||||
class SecurityIncidentResponse(SecurityIncidentBase):
|
||||
"""Schema for SecurityIncident responses with ID and timestamps."""
|
||||
|
||||
id: UUID = Field(..., description="Unique identifier for the security incident")
|
||||
created_at: datetime = Field(..., description="Timestamp when the incident was created")
|
||||
updated_at: datetime = Field(..., description="Timestamp when the incident was last updated")
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
56
api/schemas/service.py
Normal file
56
api/schemas/service.py
Normal file
@@ -0,0 +1,56 @@
|
||||
"""
|
||||
Pydantic schemas for Service model.
|
||||
|
||||
Request and response schemas for services running on infrastructure.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ServiceBase(BaseModel):
|
||||
"""Base schema with shared Service fields."""
|
||||
|
||||
infrastructure_id: Optional[str] = Field(None, description="Foreign key to infrastructure table (UUID)")
|
||||
service_name: str = Field(..., description="Name of the service (e.g., 'Gitea', 'PostgreSQL', 'Apache')")
|
||||
service_type: Optional[str] = Field(None, description="Type of service (e.g., 'git_hosting', 'database', 'web_server')")
|
||||
external_url: Optional[str] = Field(None, description="External URL for accessing the service")
|
||||
internal_url: Optional[str] = Field(None, description="Internal URL for accessing the service")
|
||||
port: Optional[int] = Field(None, description="Port number the service runs on")
|
||||
protocol: Optional[str] = Field(None, description="Protocol used (https, ssh, smb, etc.)")
|
||||
status: str = Field("running", description="Status: running, stopped, error, maintenance")
|
||||
version: Optional[str] = Field(None, description="Version of the service")
|
||||
notes: Optional[str] = Field(None, description="Additional notes")
|
||||
|
||||
|
||||
class ServiceCreate(ServiceBase):
|
||||
"""Schema for creating a new Service."""
|
||||
pass
|
||||
|
||||
|
||||
class ServiceUpdate(BaseModel):
|
||||
"""Schema for updating an existing Service. All fields are optional."""
|
||||
|
||||
infrastructure_id: Optional[str] = Field(None, description="Foreign key to infrastructure table (UUID)")
|
||||
service_name: Optional[str] = Field(None, description="Name of the service (e.g., 'Gitea', 'PostgreSQL', 'Apache')")
|
||||
service_type: Optional[str] = Field(None, description="Type of service (e.g., 'git_hosting', 'database', 'web_server')")
|
||||
external_url: Optional[str] = Field(None, description="External URL for accessing the service")
|
||||
internal_url: Optional[str] = Field(None, description="Internal URL for accessing the service")
|
||||
port: Optional[int] = Field(None, description="Port number the service runs on")
|
||||
protocol: Optional[str] = Field(None, description="Protocol used (https, ssh, smb, etc.)")
|
||||
status: Optional[str] = Field(None, description="Status: running, stopped, error, maintenance")
|
||||
version: Optional[str] = Field(None, description="Version of the service")
|
||||
notes: Optional[str] = Field(None, description="Additional notes")
|
||||
|
||||
|
||||
class ServiceResponse(ServiceBase):
|
||||
"""Schema for Service responses with ID and timestamps."""
|
||||
|
||||
id: UUID = Field(..., description="Unique identifier for the service")
|
||||
created_at: datetime = Field(..., description="Timestamp when the service was created")
|
||||
updated_at: datetime = Field(..., description="Timestamp when the service was last updated")
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
66
api/schemas/session.py
Normal file
66
api/schemas/session.py
Normal file
@@ -0,0 +1,66 @@
|
||||
"""
|
||||
Pydantic schemas for Session model.
|
||||
|
||||
Request and response schemas for work sessions with time tracking.
|
||||
"""
|
||||
|
||||
from datetime import date, datetime
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class SessionBase(BaseModel):
|
||||
"""Base schema with shared Session fields."""
|
||||
|
||||
client_id: Optional[str] = Field(None, description="Foreign key to clients table (UUID)")
|
||||
project_id: Optional[str] = Field(None, description="Foreign key to projects table (UUID)")
|
||||
machine_id: Optional[str] = Field(None, description="Foreign key to machines table (UUID)")
|
||||
session_date: date = Field(..., description="Date of the session")
|
||||
start_time: Optional[datetime] = Field(None, description="Session start timestamp")
|
||||
end_time: Optional[datetime] = Field(None, description="Session end timestamp")
|
||||
duration_minutes: Optional[int] = Field(None, description="Duration in minutes (auto-calculated or manual)")
|
||||
status: str = Field("completed", description="Session status: completed, in_progress, blocked, pending")
|
||||
session_title: str = Field(..., description="Brief title describing the session")
|
||||
summary: Optional[str] = Field(None, description="Markdown summary of the session")
|
||||
is_billable: bool = Field(False, description="Whether this session is billable")
|
||||
billable_hours: Optional[float] = Field(None, description="Billable hours if applicable")
|
||||
technician: Optional[str] = Field(None, description="Name of technician who performed the work")
|
||||
session_log_file: Optional[str] = Field(None, description="Path to markdown session log file")
|
||||
notes: Optional[str] = Field(None, description="Additional notes about the session")
|
||||
|
||||
|
||||
class SessionCreate(SessionBase):
|
||||
"""Schema for creating a new Session."""
|
||||
pass
|
||||
|
||||
|
||||
class SessionUpdate(BaseModel):
|
||||
"""Schema for updating an existing Session. All fields are optional."""
|
||||
|
||||
client_id: Optional[str] = Field(None, description="Foreign key to clients table (UUID)")
|
||||
project_id: Optional[str] = Field(None, description="Foreign key to projects table (UUID)")
|
||||
machine_id: Optional[str] = Field(None, description="Foreign key to machines table (UUID)")
|
||||
session_date: Optional[date] = Field(None, description="Date of the session")
|
||||
start_time: Optional[datetime] = Field(None, description="Session start timestamp")
|
||||
end_time: Optional[datetime] = Field(None, description="Session end timestamp")
|
||||
duration_minutes: Optional[int] = Field(None, description="Duration in minutes (auto-calculated or manual)")
|
||||
status: Optional[str] = Field(None, description="Session status: completed, in_progress, blocked, pending")
|
||||
session_title: Optional[str] = Field(None, description="Brief title describing the session")
|
||||
summary: Optional[str] = Field(None, description="Markdown summary of the session")
|
||||
is_billable: Optional[bool] = Field(None, description="Whether this session is billable")
|
||||
billable_hours: Optional[float] = Field(None, description="Billable hours if applicable")
|
||||
technician: Optional[str] = Field(None, description="Name of technician who performed the work")
|
||||
session_log_file: Optional[str] = Field(None, description="Path to markdown session log file")
|
||||
notes: Optional[str] = Field(None, description="Additional notes about the session")
|
||||
|
||||
|
||||
class SessionResponse(SessionBase):
|
||||
"""Schema for Session responses with ID and timestamps."""
|
||||
|
||||
id: UUID = Field(..., description="Unique identifier for the session")
|
||||
created_at: datetime = Field(..., description="Timestamp when the session was created")
|
||||
updated_at: datetime = Field(..., description="Timestamp when the session was last updated")
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
52
api/schemas/site.py
Normal file
52
api/schemas/site.py
Normal file
@@ -0,0 +1,52 @@
|
||||
"""
|
||||
Pydantic schemas for Site model.
|
||||
|
||||
Request and response schemas for client physical locations.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class SiteBase(BaseModel):
|
||||
"""Base schema with shared Site fields."""
|
||||
|
||||
client_id: UUID = Field(..., description="Reference to the client this site belongs to")
|
||||
name: str = Field(..., description="Site name (e.g., 'Main Office', 'SLC - Salt Lake City')")
|
||||
network_subnet: Optional[str] = Field(None, description="Network subnet for the site (e.g., '172.16.9.0/24')")
|
||||
vpn_required: bool = Field(False, description="Whether VPN is required to access this site")
|
||||
vpn_subnet: Optional[str] = Field(None, description="VPN subnet if applicable (e.g., '192.168.1.0/24')")
|
||||
gateway_ip: Optional[str] = Field(None, description="Gateway IP address (IPv4 or IPv6)")
|
||||
dns_servers: Optional[str] = Field(None, description="JSON array of DNS server addresses")
|
||||
notes: Optional[str] = Field(None, description="Additional notes about the site")
|
||||
|
||||
|
||||
class SiteCreate(SiteBase):
|
||||
"""Schema for creating a new Site."""
|
||||
pass
|
||||
|
||||
|
||||
class SiteUpdate(BaseModel):
|
||||
"""Schema for updating an existing Site. All fields are optional."""
|
||||
|
||||
client_id: Optional[UUID] = Field(None, description="Reference to the client this site belongs to")
|
||||
name: Optional[str] = Field(None, description="Site name (e.g., 'Main Office', 'SLC - Salt Lake City')")
|
||||
network_subnet: Optional[str] = Field(None, description="Network subnet for the site (e.g., '172.16.9.0/24')")
|
||||
vpn_required: Optional[bool] = Field(None, description="Whether VPN is required to access this site")
|
||||
vpn_subnet: Optional[str] = Field(None, description="VPN subnet if applicable (e.g., '192.168.1.0/24')")
|
||||
gateway_ip: Optional[str] = Field(None, description="Gateway IP address (IPv4 or IPv6)")
|
||||
dns_servers: Optional[str] = Field(None, description="JSON array of DNS server addresses")
|
||||
notes: Optional[str] = Field(None, description="Additional notes about the site")
|
||||
|
||||
|
||||
class SiteResponse(SiteBase):
|
||||
"""Schema for Site responses with ID and timestamps."""
|
||||
|
||||
id: UUID = Field(..., description="Unique identifier for the site")
|
||||
created_at: datetime = Field(..., description="Timestamp when the site was created")
|
||||
updated_at: datetime = Field(..., description="Timestamp when the site was last updated")
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
47
api/schemas/tag.py
Normal file
47
api/schemas/tag.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""
|
||||
Pydantic schemas for Tag model.
|
||||
|
||||
Request and response schemas for categorizing and organizing work items.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class TagBase(BaseModel):
|
||||
"""Base schema with shared Tag fields."""
|
||||
|
||||
name: str = Field(..., description="Tag name (unique)")
|
||||
category: Optional[str] = Field(None, description="Tag category: technology, client, infrastructure, problem_type, action, service")
|
||||
description: Optional[str] = Field(None, description="Description of the tag")
|
||||
usage_count: int = Field(0, description="Number of times this tag has been used (auto-incremented)")
|
||||
|
||||
|
||||
class TagCreate(BaseModel):
|
||||
"""Schema for creating a new Tag. usage_count is not user-provided."""
|
||||
|
||||
name: str = Field(..., description="Tag name (unique)")
|
||||
category: Optional[str] = Field(None, description="Tag category: technology, client, infrastructure, problem_type, action, service")
|
||||
description: Optional[str] = Field(None, description="Description of the tag")
|
||||
|
||||
|
||||
class TagUpdate(BaseModel):
|
||||
"""Schema for updating an existing Tag. All fields are optional."""
|
||||
|
||||
name: Optional[str] = Field(None, description="Tag name (unique)")
|
||||
category: Optional[str] = Field(None, description="Tag category: technology, client, infrastructure, problem_type, action, service")
|
||||
description: Optional[str] = Field(None, description="Description of the tag")
|
||||
usage_count: Optional[int] = Field(None, description="Number of times this tag has been used (auto-incremented)")
|
||||
|
||||
|
||||
class TagResponse(TagBase):
|
||||
"""Schema for Tag responses with ID and timestamps."""
|
||||
|
||||
id: UUID = Field(..., description="Unique identifier for the tag")
|
||||
created_at: datetime = Field(..., description="Timestamp when the tag was created")
|
||||
updated_at: datetime = Field(..., description="Timestamp when the tag was last updated")
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
86
api/schemas/task.py
Normal file
86
api/schemas/task.py
Normal file
@@ -0,0 +1,86 @@
|
||||
"""
|
||||
Pydantic schemas for Task model.
|
||||
|
||||
Request and response schemas for hierarchical task tracking.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class TaskBase(BaseModel):
|
||||
"""Base schema with shared Task fields."""
|
||||
|
||||
parent_task_id: Optional[str] = Field(None, description="Reference to parent task for hierarchical structure (UUID)")
|
||||
task_order: int = Field(..., description="Order of this task relative to siblings")
|
||||
title: str = Field(..., description="Task title", max_length=500)
|
||||
description: Optional[str] = Field(None, description="Detailed task description")
|
||||
task_type: Optional[str] = Field(
|
||||
None,
|
||||
description="Type: implementation, research, review, deployment, testing, documentation, bugfix, analysis"
|
||||
)
|
||||
status: str = Field(
|
||||
...,
|
||||
description="Status: pending, in_progress, blocked, completed, cancelled"
|
||||
)
|
||||
blocking_reason: Optional[str] = Field(None, description="Reason why task is blocked (if status='blocked')")
|
||||
session_id: Optional[str] = Field(None, description="Foreign key to sessions table (UUID)")
|
||||
client_id: Optional[str] = Field(None, description="Foreign key to clients table (UUID)")
|
||||
project_id: Optional[str] = Field(None, description="Foreign key to projects table (UUID)")
|
||||
assigned_agent: Optional[str] = Field(None, description="Which agent is handling this task", max_length=100)
|
||||
estimated_complexity: Optional[str] = Field(
|
||||
None,
|
||||
description="Complexity: trivial, simple, moderate, complex, very_complex"
|
||||
)
|
||||
started_at: Optional[datetime] = Field(None, description="When the task was started")
|
||||
completed_at: Optional[datetime] = Field(None, description="When the task was completed")
|
||||
task_context: Optional[str] = Field(None, description="Detailed context for this task (JSON)")
|
||||
dependencies: Optional[str] = Field(None, description="JSON array of dependency task IDs")
|
||||
|
||||
|
||||
class TaskCreate(TaskBase):
|
||||
"""Schema for creating a new Task."""
|
||||
pass
|
||||
|
||||
|
||||
class TaskUpdate(BaseModel):
|
||||
"""Schema for updating an existing Task. All fields are optional."""
|
||||
|
||||
parent_task_id: Optional[str] = Field(None, description="Reference to parent task for hierarchical structure (UUID)")
|
||||
task_order: Optional[int] = Field(None, description="Order of this task relative to siblings")
|
||||
title: Optional[str] = Field(None, description="Task title", max_length=500)
|
||||
description: Optional[str] = Field(None, description="Detailed task description")
|
||||
task_type: Optional[str] = Field(
|
||||
None,
|
||||
description="Type: implementation, research, review, deployment, testing, documentation, bugfix, analysis"
|
||||
)
|
||||
status: Optional[str] = Field(
|
||||
None,
|
||||
description="Status: pending, in_progress, blocked, completed, cancelled"
|
||||
)
|
||||
blocking_reason: Optional[str] = Field(None, description="Reason why task is blocked (if status='blocked')")
|
||||
session_id: Optional[str] = Field(None, description="Foreign key to sessions table (UUID)")
|
||||
client_id: Optional[str] = Field(None, description="Foreign key to clients table (UUID)")
|
||||
project_id: Optional[str] = Field(None, description="Foreign key to projects table (UUID)")
|
||||
assigned_agent: Optional[str] = Field(None, description="Which agent is handling this task", max_length=100)
|
||||
estimated_complexity: Optional[str] = Field(
|
||||
None,
|
||||
description="Complexity: trivial, simple, moderate, complex, very_complex"
|
||||
)
|
||||
started_at: Optional[datetime] = Field(None, description="When the task was started")
|
||||
completed_at: Optional[datetime] = Field(None, description="When the task was completed")
|
||||
task_context: Optional[str] = Field(None, description="Detailed context for this task (JSON)")
|
||||
dependencies: Optional[str] = Field(None, description="JSON array of dependency task IDs")
|
||||
|
||||
|
||||
class TaskResponse(TaskBase):
|
||||
"""Schema for Task responses with ID and timestamps."""
|
||||
|
||||
id: UUID = Field(..., description="Unique identifier for the task")
|
||||
created_at: datetime = Field(..., description="Timestamp when the task was created")
|
||||
updated_at: datetime = Field(..., description="Timestamp when the task was last updated")
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
91
api/schemas/work_item.py
Normal file
91
api/schemas/work_item.py
Normal file
@@ -0,0 +1,91 @@
|
||||
"""
|
||||
Pydantic schemas for WorkItem model.
|
||||
|
||||
Request and response schemas for work items tracking session activities.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class WorkItemBase(BaseModel):
|
||||
"""Base schema with shared WorkItem fields."""
|
||||
|
||||
session_id: str = Field(..., description="Foreign key to sessions table (UUID)")
|
||||
category: str = Field(
|
||||
...,
|
||||
description="Work category: infrastructure, troubleshooting, configuration, development, maintenance, security, documentation"
|
||||
)
|
||||
title: str = Field(..., description="Brief title of the work item")
|
||||
description: str = Field(..., description="Detailed description of the work performed")
|
||||
status: str = Field(
|
||||
"completed",
|
||||
description="Status: completed, in_progress, blocked, pending, deferred"
|
||||
)
|
||||
priority: Optional[str] = Field(
|
||||
None,
|
||||
description="Priority level: critical, high, medium, low"
|
||||
)
|
||||
is_billable: bool = Field(False, description="Whether this work item is billable")
|
||||
estimated_minutes: Optional[int] = Field(None, description="Estimated time to complete in minutes")
|
||||
actual_minutes: Optional[int] = Field(None, description="Actual time spent in minutes")
|
||||
affected_systems: Optional[str] = Field(
|
||||
None,
|
||||
description='JSON array of affected systems (e.g., ["jupiter", "172.16.3.20"])'
|
||||
)
|
||||
technologies_used: Optional[str] = Field(
|
||||
None,
|
||||
description='JSON array of technologies used (e.g., ["docker", "mariadb"])'
|
||||
)
|
||||
item_order: Optional[int] = Field(None, description="Sequence order within the session")
|
||||
completed_at: Optional[datetime] = Field(None, description="When the work item was completed")
|
||||
|
||||
|
||||
class WorkItemCreate(WorkItemBase):
|
||||
"""Schema for creating a new WorkItem."""
|
||||
pass
|
||||
|
||||
|
||||
class WorkItemUpdate(BaseModel):
|
||||
"""Schema for updating an existing WorkItem. All fields are optional."""
|
||||
|
||||
session_id: Optional[str] = Field(None, description="Foreign key to sessions table (UUID)")
|
||||
category: Optional[str] = Field(
|
||||
None,
|
||||
description="Work category: infrastructure, troubleshooting, configuration, development, maintenance, security, documentation"
|
||||
)
|
||||
title: Optional[str] = Field(None, description="Brief title of the work item")
|
||||
description: Optional[str] = Field(None, description="Detailed description of the work performed")
|
||||
status: Optional[str] = Field(
|
||||
None,
|
||||
description="Status: completed, in_progress, blocked, pending, deferred"
|
||||
)
|
||||
priority: Optional[str] = Field(
|
||||
None,
|
||||
description="Priority level: critical, high, medium, low"
|
||||
)
|
||||
is_billable: Optional[bool] = Field(None, description="Whether this work item is billable")
|
||||
estimated_minutes: Optional[int] = Field(None, description="Estimated time to complete in minutes")
|
||||
actual_minutes: Optional[int] = Field(None, description="Actual time spent in minutes")
|
||||
affected_systems: Optional[str] = Field(
|
||||
None,
|
||||
description='JSON array of affected systems (e.g., ["jupiter", "172.16.3.20"])'
|
||||
)
|
||||
technologies_used: Optional[str] = Field(
|
||||
None,
|
||||
description='JSON array of technologies used (e.g., ["docker", "mariadb"])'
|
||||
)
|
||||
item_order: Optional[int] = Field(None, description="Sequence order within the session")
|
||||
completed_at: Optional[datetime] = Field(None, description="When the work item was completed")
|
||||
|
||||
|
||||
class WorkItemResponse(WorkItemBase):
|
||||
"""Schema for WorkItem responses with ID and timestamps."""
|
||||
|
||||
id: UUID = Field(..., description="Unique identifier for the work item")
|
||||
created_at: datetime = Field(..., description="Timestamp when the work item was created")
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
Reference in New Issue
Block a user