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:
@@ -0,0 +1,43 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
|
||||
from sqlalchemy import Index, String, Text, text
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from core.db.base import Base, TimestampMixin
|
||||
|
||||
|
||||
class UserFeedback(TimestampMixin, Base):
|
||||
__tablename__ = "user_feedback"
|
||||
__table_args__ = (
|
||||
Index("ix_user_feedback_user_id", "user_id"),
|
||||
Index("ix_user_feedback_created_at", "created_at"),
|
||||
Index("ix_user_feedback_status", "status"),
|
||||
)
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
server_default=text("gen_random_uuid()"),
|
||||
primary_key=True,
|
||||
)
|
||||
user_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
nullable=True,
|
||||
)
|
||||
feedback_type: Mapped[str] = mapped_column(
|
||||
String(20), nullable=False, server_default="other"
|
||||
)
|
||||
content: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
images: Mapped[list[str]] = mapped_column(
|
||||
JSONB, nullable=False, server_default=text("'[]'::jsonb"), default=list
|
||||
)
|
||||
device_info: Mapped[dict] = mapped_column(
|
||||
JSONB, nullable=False, server_default=text("'{}'::jsonb"), default=dict
|
||||
)
|
||||
app_version: Mapped[str] = mapped_column(String(20), nullable=False)
|
||||
os_version: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
status: Mapped[str] = mapped_column(
|
||||
String(20), nullable=False, server_default="pending"
|
||||
)
|
||||
Reference in New Issue
Block a user