6a2a9d2c87
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
44 lines
1.5 KiB
Python
44 lines
1.5 KiB
Python
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"
|
|
)
|