feat: implement 8 OpenSpec proposals for security, reliability, and UX improvements

## Security Enhancements (P0)
- Add input validation with max_length and numeric range constraints
- Implement WebSocket token authentication via first message
- Add path traversal prevention in file storage service

## Permission Enhancements (P0)
- Add project member management for cross-department access
- Implement is_department_manager flag for workload visibility

## Cycle Detection (P0)
- Add DFS-based cycle detection for task dependencies
- Add formula field circular reference detection
- Display user-friendly cycle path visualization

## Concurrency & Reliability (P1)
- Implement optimistic locking with version field (409 Conflict on mismatch)
- Add trigger retry mechanism with exponential backoff (1s, 2s, 4s)
- Implement cascade restore for soft-deleted tasks

## Rate Limiting (P1)
- Add tiered rate limits: standard (60/min), sensitive (20/min), heavy (5/min)
- Apply rate limits to tasks, reports, attachments, and comments

## Frontend Improvements (P1)
- Add responsive sidebar with hamburger menu for mobile
- Improve touch-friendly UI with proper tap target sizes
- Complete i18n translations for all components

## Backend Reliability (P2)
- Configure database connection pool (size=10, overflow=20)
- Add Redis fallback mechanism with message queue
- Add blocker check before task deletion

## API Enhancements (P3)
- Add standardized response wrapper utility
- Add /health/ready and /health/live endpoints
- Implement project templates with status/field copying

## Tests Added
- test_input_validation.py - Schema and path traversal tests
- test_concurrency_reliability.py - Optimistic locking and retry tests
- test_backend_reliability.py - Connection pool and Redis tests
- test_api_enhancements.py - Health check and template tests

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
beabigegg
2026-01-10 22:13:43 +08:00
parent 96210c7ad4
commit 3bdc6ff1c9
106 changed files with 9704 additions and 429 deletions

View File

@@ -0,0 +1,125 @@
"""Project Template model for reusable project configurations.
Allows users to create templates with predefined task statuses and custom fields
that can be used to quickly set up new projects.
"""
import uuid
from sqlalchemy import Column, String, Text, Boolean, DateTime, ForeignKey, JSON
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from app.core.database import Base
class ProjectTemplate(Base):
"""Template for creating projects with predefined configurations.
A template stores:
- Basic project metadata (name, description)
- Predefined task statuses (stored as JSON)
- Predefined custom field definitions (stored as JSON)
When a project is created from a template, the TaskStatus and CustomField
records are copied to the new project.
"""
__tablename__ = "pjctrl_project_templates"
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
name = Column(String(200), nullable=False)
description = Column(Text, nullable=True)
# Template owner
owner_id = Column(String(36), ForeignKey("pjctrl_users.id"), nullable=False)
# Whether the template is available to all users or just the owner
is_public = Column(Boolean, default=False, nullable=False)
# Soft delete flag
is_active = Column(Boolean, default=True, nullable=False)
# Predefined task statuses as JSON array
# Format: [{"name": "To Do", "color": "#808080", "position": 0, "is_done": false}, ...]
task_statuses = Column(JSON, nullable=True)
# Predefined custom field definitions as JSON array
# Format: [{"name": "Priority", "field_type": "dropdown", "options": [...], ...}, ...]
custom_fields = Column(JSON, nullable=True)
# Optional default project settings
default_security_level = Column(String(20), default="department", nullable=True)
created_at = Column(DateTime, server_default=func.now(), nullable=False)
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), nullable=False)
# Relationships
owner = relationship("User", foreign_keys=[owner_id])
# Default template data for system templates
SYSTEM_TEMPLATES = [
{
"name": "Basic Project",
"description": "A simple project template with standard task statuses.",
"is_public": True,
"task_statuses": [
{"name": "To Do", "color": "#808080", "position": 0, "is_done": False},
{"name": "In Progress", "color": "#0066cc", "position": 1, "is_done": False},
{"name": "Done", "color": "#00cc66", "position": 2, "is_done": True},
],
"custom_fields": [],
},
{
"name": "Software Development",
"description": "Template for software development projects with extended workflow.",
"is_public": True,
"task_statuses": [
{"name": "Backlog", "color": "#808080", "position": 0, "is_done": False},
{"name": "To Do", "color": "#3366cc", "position": 1, "is_done": False},
{"name": "In Progress", "color": "#0066cc", "position": 2, "is_done": False},
{"name": "Code Review", "color": "#cc6600", "position": 3, "is_done": False},
{"name": "Testing", "color": "#9933cc", "position": 4, "is_done": False},
{"name": "Done", "color": "#00cc66", "position": 5, "is_done": True},
],
"custom_fields": [
{
"name": "Story Points",
"field_type": "number",
"is_required": False,
"position": 0,
},
{
"name": "Sprint",
"field_type": "dropdown",
"options": ["Sprint 1", "Sprint 2", "Sprint 3", "Backlog"],
"is_required": False,
"position": 1,
},
],
},
{
"name": "Marketing Campaign",
"description": "Template for marketing campaign management.",
"is_public": True,
"task_statuses": [
{"name": "Planning", "color": "#808080", "position": 0, "is_done": False},
{"name": "Content Creation", "color": "#cc6600", "position": 1, "is_done": False},
{"name": "Review", "color": "#9933cc", "position": 2, "is_done": False},
{"name": "Scheduled", "color": "#0066cc", "position": 3, "is_done": False},
{"name": "Published", "color": "#00cc66", "position": 4, "is_done": True},
],
"custom_fields": [
{
"name": "Channel",
"field_type": "dropdown",
"options": ["Email", "Social Media", "Website", "Print", "Event"],
"is_required": False,
"position": 0,
},
{
"name": "Target Audience",
"field_type": "text",
"is_required": False,
"position": 1,
},
],
},
]