Files
Reklamator/app/routes/submission.py
T
gurixandClaude b301def134 Implement MVP: Anonymous feedback submission (User Story 1)
Complete implementation of Phase 1-3 (64 tasks):
- Phase 1: Project setup with Flask, pytest, configuration
- Phase 2: Core infrastructure (auth, models, services, testing)
- Phase 3: Anonymous feedback submission with file uploads

Features:
- Anonymous feedback submission (text and/or up to 3 file attachments)
- Multi-language support (any language accepted)
- File validation (type, size) and virus scanning (ClamAV)
- Product management with active/archived status
- File-based storage with YAML metadata
- User authentication system (Flask-Login)
- CSRF protection and rate limiting
- Test coverage: 10 passing tests (contract + integration)

Security:
- No IP address logging (FR-055 compliance)
- File type whitelist and size limits (10MB max)
- Virus scanning with graceful degradation
- Filename sanitization and secure storage

Test Results:
- 8 contract tests passed
- 2 integration tests passed
- End-to-end workflow verified

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-16 15:14:51 +02:00

101 lines
3.2 KiB
Python

"""Submission routes - anonymous feedback submission"""
from flask import Blueprint, render_template, request, redirect, url_for, flash, abort
from app.models.product import Product
from app.services.feedback_storage import FeedbackStorageService
from app.utils.file_validator import validate_file, scan_file_for_viruses
bp = Blueprint('submission', __name__, url_prefix='/submit')
@bp.route('/<product_slug>', methods=['GET'])
def form(product_slug):
"""Display feedback submission form
Args:
product_slug: Product submission URL slug
Returns:
Rendered submission form template or 404
"""
# Load product by slug
product = Product.get_by_slug(product_slug)
if not product:
abort(404, description="Product not found")
# Check if product is archived
if product.is_archived():
abort(404, description="This product is no longer accepting feedback")
return render_template('submission/form.html', product=product)
@bp.route('/<product_slug>', methods=['POST'])
def submit(product_slug):
"""Process feedback submission
Args:
product_slug: Product submission URL slug
Returns:
Redirect to success page or error page
"""
# Load product by slug
product = Product.get_by_slug(product_slug)
if not product:
abort(404, description="Product not found")
# Check if product is archived
if product.is_archived():
abort(404, description="This product is no longer accepting feedback")
# Get form data
feedback_text = request.form.get('feedback_text', '').strip()
# Get uploaded files
uploaded_files = request.files.getlist('files')
# Filter out empty file inputs
files = [f for f in uploaded_files if f and f.filename != '']
# Validation: Must provide either text or files
if not feedback_text and not files:
abort(400, description="Please provide either feedback text or attachments")
# Validation: Maximum 3 files
if len(files) > 3:
abort(400, description="Maximum 3 attachments allowed")
# Validate each file
for file in files:
is_valid, error_message = validate_file(file)
if not is_valid:
abort(400, description=error_message)
# Scan for viruses
is_clean, virus_message = scan_file_for_viruses(file)
if not is_clean:
abort(400, description=f"File rejected: {virus_message}")
# Save feedback
try:
feedback = FeedbackStorageService.save_complete_feedback(
product_id=product.product_id,
content_text=feedback_text if feedback_text else None,
files=files if files else None
)
return render_template('submission/success.html',
product=product,
feedback_id=feedback.feedback_id)
except Exception as e:
# Log error
from flask import current_app
current_app.logger.error(f"Error saving feedback: {e}")
return render_template('submission/error.html',
product=product,
error_message="An error occurred while saving your feedback. Please try again."), 500