refactor(feedback): align package + DB enums, plan central hub

Macht @mana/feedback zur SSOT für alle Nutzer-Feedback-Categories und
-Status — Voraussetzung dafür, dass Onboarding-Wishes, NPS, Churn-Feedback
etc. künftig dort landen.

- Status-Enum: DB-Werte umbenannt new/reviewed/done/rejected →
  submitted/under_review/completed/declined (Package gewinnt). PG≥10
  ALTER TYPE … RENAME VALUE ist non-destructive.
- Category 'praise' ins Package aufgenommen (war nur in DB).
- Category 'onboarding-wish' neu in Package + DB für den Wish-Step.
- Default status in DB: 'new' → 'submitted'.
- CreateFeedbackInput.isPublic optional → Service reicht durch, default
  bleibt true; private Categories wie onboarding-wish setzen false.
- Schema-Datei mit SSOT-Kommentar versehen, der Drift in Zukunft verhindert.

Hand-authored Migration unter services/mana-analytics/drizzle/0001_*.sql
weil drizzle-kit push Enum-Werte nicht zuverlässig umbenennt. Manuell
einspielen vor nächstem db:push:

  psql "\$DATABASE_URL" -f services/mana-analytics/drizzle/0001_align-feedback-enums.sql

Plan in docs/plans/feedback-hub.md (Phase 0–4); Phase 0 + 1 jetzt, 2-4
deferred.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-26 21:52:25 +02:00
parent bf3bca268a
commit ba6274edbe
6 changed files with 339 additions and 7 deletions

View file

@ -13,22 +13,27 @@ import {
export const feedbackSchema = pgSchema('feedback');
// Enum values must mirror @mana/feedback's FeedbackCategory / FeedbackStatus
// unions exactly. Renames or additions need a hand-authored SQL migration
// under services/mana-analytics/drizzle/ (drizzle-kit push can't safely
// rename enum values).
export const feedbackCategoryEnum = pgEnum('feedback_category', [
'bug',
'feature',
'improvement',
'question',
'praise',
'onboarding-wish',
'other',
]);
export const feedbackStatusEnum = pgEnum('feedback_status', [
'new',
'reviewed',
'submitted',
'under_review',
'planned',
'in_progress',
'done',
'rejected',
'completed',
'declined',
]);
export const userFeedback = feedbackSchema.table(
@ -40,7 +45,7 @@ export const userFeedback = feedbackSchema.table(
title: text('title'),
feedbackText: text('feedback_text').notNull(),
category: feedbackCategoryEnum('category').default('other').notNull(),
status: feedbackStatusEnum('status').default('new').notNull(),
status: feedbackStatusEnum('status').default('submitted').notNull(),
isPublic: boolean('is_public').default(true).notNull(),
adminResponse: text('admin_response'),
voteCount: integer('vote_count').default(0).notNull(),

View file

@ -20,6 +20,7 @@ export class FeedbackService {
feedbackText: string;
category?: string;
title?: string;
isPublic?: boolean;
deviceInfo?: Record<string, unknown>;
}
) {
@ -42,6 +43,10 @@ export class FeedbackService {
title: title || data.feedbackText.slice(0, 80),
feedbackText: data.feedbackText,
category: (data.category as any) || 'other',
// Honor explicit isPublic from caller; otherwise let the column
// default (true) apply. Private intake categories like
// 'onboarding-wish' should pass `false`.
...(typeof data.isPublic === 'boolean' ? { isPublic: data.isPublic } : {}),
deviceInfo: data.deviceInfo,
})
.returning();