mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-23 14:26:41 +02:00
feat(feedback,credits): community-credit grants — +5 submit / +500 ship / +25 reaction-match
Phase 3.A des feedback-rewards-and-identity-Plans. Direkter Reziprozitäts- Loop: User kriegt sofort etwas zurück fürs Mitwirken, Originalwunsch- Eulen werden beim Ship belohnt, Reagierer kriegen einen Anteil. mana-credits: - Neuer Endpoint POST /api/v1/internal/credits/grant + grantCredits() Service-Methode mit Idempotency via metadata.referenceId. - transaction_type-Enum erweitert um 'grant' (eigener Typ statt Mismatch mit 'refund'). - Migration 0001_grant_transaction_type.sql + partial-Index auf metadata->>'referenceId' für O(log n) Idempotency-Lookup. mana-analytics: - FeedbackService stempelt sofort +5 Credits beim createFeedback (top- level only, Replies bekommen nichts), wenn Mindest-20-Zeichen erfüllt und Rate-Limit (10/User/24h via feedback_grant_log) nicht überschritten. - adminUpdate triggert beim FRISCHEN Übergang nach 'completed': +500 Credits an Original-Wisher + +25 an alle, die mit 👍 oder 🚀 reagiert haben. Doppel-Pay strukturell unmöglich via referenceId (`<id>_shipped`, `<id>_reaction_<userId>`). - Founder-Whitelist via FEEDBACK_FOUNDER_USER_IDS env (verhindert Self-Reward). - Drop voteCount-Spalte (durch reactions/score seit 0002 ersetzt). - Migration 0003_grant_log_drop_vote_count.sql idempotent, lokal + prod eingespielt. Plan: docs/plans/feedback-rewards-and-identity.md (Phase 3.A-3.F). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e712faf7b7
commit
dbe24acfc4
12 changed files with 882 additions and 7 deletions
|
|
@ -49,7 +49,6 @@ export const userFeedback = feedbackSchema.table(
|
|||
status: feedbackStatusEnum('status').default('submitted').notNull(),
|
||||
isPublic: boolean('is_public').default(true).notNull(),
|
||||
adminResponse: text('admin_response'),
|
||||
voteCount: integer('vote_count').default(0).notNull(),
|
||||
// Public-community fields (Phase 2.1):
|
||||
// `display_hash` = SHA256(userId + serviceKey), never exposed.
|
||||
// `display_name` = deterministic Tier-pseudonym derived from hash.
|
||||
|
|
@ -105,5 +104,20 @@ export const feedbackReactions = feedbackSchema.table(
|
|||
})
|
||||
);
|
||||
|
||||
// Append-only log of community-credit grants. Used as a sliding-window
|
||||
// rate-limit counter ("max 10 grants per user per 24h") and as an audit
|
||||
// trail. Cleanup of rows older than 7d is handled by a nightly cron.
|
||||
export const feedbackGrantLog = feedbackSchema.table(
|
||||
'feedback_grant_log',
|
||||
{
|
||||
userId: text('user_id').notNull(),
|
||||
grantedAt: timestamp('granted_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
reason: text('reason').notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
recentIdx: index('feedback_grant_log_recent_idx').on(table.userId, table.grantedAt),
|
||||
})
|
||||
);
|
||||
|
||||
export type Feedback = typeof userFeedback.$inferSelect;
|
||||
export type FeedbackReaction = typeof feedbackReactions.$inferSelect;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue