Files
eryao/docs/protocols/feedback/feedback-protocol.md
T
qzl 6a2a9d2c87 feat(feedback): implement user feedback collection system with email reporting
Backend:
- Add user_feedback table with RLS policy
- Create feedback submission API (multipart/form-data)
- Implement xlsx report generation with embedded images
- Add scheduled email delivery via Feishu SMTP
- Create HTML email templates (daily_report, no_feedback)

Frontend:
- Add feedback screen with type selection and image picker
- Support anonymous submission via skipAuth flag
- Collect device info and app version

Protocol:
- Document feedback API contract and error codes
- Update http-error-codes.md with FEEDBACK_* codes
2026-04-20 12:49:54 +08:00

5.8 KiB

Feedback Protocol (Frontend <-> Backend)

This document defines the canonical backend contract for user feedback submission.

Protocol verification status:

  • Backend route source: backend/src/v1/feedback/router.py
  • Backend schema source: backend/src/v1/feedback/schemas.py
  • Backend service source: backend/src/v1/feedback/service.py
  • Backend dependencies source: backend/src/v1/feedback/dependencies.py
  • Backend model source: backend/src/models/user_feedback.py
  • Backend report source: backend/src/v1/feedback/report.py
  • Backend tasks source: backend/src/v1/feedback/tasks.py
  • Backend email source: backend/src/core/email/sender.py
  • Frontend mapping source: apps/lib/features/settings/data/apis/feedback_api.dart
  • Storage config source: backend/src/core/config/settings.py
  • Current status: Phase 1 + Phase 2 + Phase 3 implemented

Compatibility strategy

  • Current strategy: additive evolution (backward-compatible).
  • Breaking change requires explicit migration + rollback notes (requires-migration).

Route overview

  • Submit feedback: POST /api/v1/feedback (multipart/form-data)

Auth and trust boundary

  • Feedback submission supports both authenticated and anonymous modes.
  • Authenticated: Authorization: Bearer {token} header. Backend extracts user_id from JWT.
  • Anonymous: No Authorization header. user_id stored as NULL.
  • Frontend controls anonymity via checkbox ("Do not upload my personal information").

Submit feedback

Request

POST /api/v1/feedback
Content-Type: multipart/form-data
Authorization: Bearer {token}  # Optional

Form Fields:
- feedback_type: "bug" | "suggestion" | "other"  (required)
- content: string (required, 1-500 chars after trim)
- device_info: string (JSON, e.g. {"platform":"ios","model":"iPhone 15"})
- app_version: string (required)
- os_version: string (required)

Files:
- images: File[] (optional, max 3 files, jpg/png only, max 5MB each)

Response 201

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "created_at": "2026-04-17T10:30:00+00:00"
}

Error codes

code status meaning
FEEDBACK_CONTENT_EMPTY 400 Content is empty or whitespace-only
FEEDBACK_CONTENT_TOO_LONG 400 Content exceeds 500 characters
FEEDBACK_TOO_MANY_IMAGES 400 More than 3 images uploaded
FEEDBACK_IMAGE_TOO_LARGE 400 Single image exceeds 5MB
FEEDBACK_INVALID_IMAGE_TYPE 400 Image type not jpg/png
FEEDBACK_SUBMIT_FAILED 500 Internal storage or database failure
REQUEST_VALIDATION_ERROR 422 Invalid feedback_type or device_info JSON

Database schema

Table: user_feedback

Column Type Nullable Default Description
id UUID NO gen_random_uuid() Primary key
user_id UUID YES - FK to auth.users, NULL = anonymous
feedback_type VARCHAR(20) NO 'other' bug/suggestion/other
content TEXT NO - Feedback content, max 500 chars
images JSONB NO '[]' Storage path list, max 3
device_info JSONB NO '{}' Device info JSON
app_version VARCHAR(20) NO - App version string
os_version VARCHAR(50) NO - OS version string
status VARCHAR(20) NO 'pending' pending/processed
created_at TIMESTAMPTZ NO now() Creation timestamp
updated_at TIMESTAMPTZ NO now() Last update timestamp

Storage

  • Bucket: configured via ERYAO_STORAGE__FEEDBACK__BUCKET (default: feedback-images)
  • Visibility: private (no public access needed)
  • Path pattern: {YYYY-MM-DD}/{timestamp}_{index}.{ext}
  • Max file size: configured via ERYAO_STORAGE__FEEDBACK__MAX_SIZE_MB (default: 5)
  • Allowed types: image/jpeg, image/png

Frontend implementation notes

  • Uses image_picker package for image selection
  • Uses dio FormData for multipart upload
  • Anonymous mode: uses Options(extra: {'skipAuth': true}) to bypass token injection in ApiClient interceptor
  • Client-side validation: content required, max 500 chars, max 3 images
  • Server-side validation: all field validation happens in FeedbackService

Phase 2: Report Generation (Implemented)

  • Report format: xlsx (openpyxl) with embedded images
  • Image columns: Screenshots anchored to columns K/L/M
  • Report path: {log_dir}/reports/{YYYYMMDD_HHMMSS}_feedback_report.xlsx
  • Status transition: pending -> processed after report generation

Phase 3: Email Delivery (Implemented)

  • Email service: core/email/sender.py (aiosmtplib, stateless)
  • SMTP provider: Feishu enterprise email (smtp.feishu.cn:465)
  • Templates: core/email/templates/feedback/
    • daily_report.html: HTML email with stats cards (sent when feedbacks exist)
    • no_feedback.html: Plain email (sent when no feedbacks in time range)
  • Attachment naming: {YYYYMMDD_HHMMSS}_feedback_report.xlsx
  • Schedule: Configured via ERYAO_FEEDBACK_REPORT__CRON (default: 0 10 * * *)
  • Enable flag: ERYAO_FEEDBACK_REPORT__ENABLED (default: false)

Email configuration

ERYAO_EMAIL__HOST=smtp.feishu.cn
ERYAO_EMAIL__PORT=465
ERYAO_EMAIL__USE_SSL=true
ERYAO_EMAIL__USERNAME=<email address>
ERYAO_EMAIL__PASSWORD=<SMTP password>
ERYAO_EMAIL__FROM_ADDRESS=<email address>
ERYAO_EMAIL__FROM_NAME=Eryao Feedback System
ERYAO_FEEDBACK_REPORT__EMAIL=<recipient email>

Email template variables

Variable Description
${start_date} Report period start date (YYYY-MM-DD)
${start_hour} Report period start hour
${end_date} Report period end date (YYYY-MM-DD)
${end_hour} Report period end hour
${total_count} Total feedback count
${bug_count} Bug type count
${suggestion_count} Suggestion type count
${generated_at} Report generation timestamp