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
This commit is contained in:
qzl
2026-04-20 12:49:54 +08:00
parent 913ed26f8d
commit 6a2a9d2c87
46 changed files with 4768 additions and 9 deletions
+49
View File
@@ -0,0 +1,49 @@
from __future__ import annotations
import asyncio
from typing import Annotated
from uuid import UUID
from fastapi import Depends, Header
from sqlalchemy.ext.asyncio import AsyncSession
from core.auth.models import CurrentUser
from core.db import get_db
from services.base.supabase import supabase_service
from v1.feedback.repository import FeedbackRepository
from v1.feedback.service import FeedbackService
async def get_optional_user(
authorization: str | None = Header(default=None),
) -> CurrentUser | None:
if not authorization:
return None
scheme, _, token = authorization.partition(" ")
if scheme.lower() != "bearer" or not token:
return None
try:
client = supabase_service.get_client()
response = await asyncio.to_thread(client.auth.get_user, token)
user = getattr(response, "user", None)
user_id = getattr(user, "id", None)
if not isinstance(user_id, str) or not user_id:
return None
return CurrentUser(
id=UUID(user_id),
email=getattr(user, "email", None),
role=getattr(user, "role", None),
)
except Exception:
return None
def get_feedback_service(
session: Annotated[AsyncSession, Depends(get_db)],
) -> FeedbackService:
return FeedbackService(
repository=FeedbackRepository(session=session),
storage=supabase_service,
)