mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-23 19:06:42 +02:00
feat(feedback): rename community-identity columns + settings-section
Letzter "community"-Rest aus dem Feedback-Hub räumt sich auf — DB-Spalten, Settings-Search-Index, Section-Name und i18n-Keys einheitlich auf "feedback": - DB: auth.users.community_show_real_name → feedback_show_real_name, community_karma → feedback_karma. Migration unter services/mana-auth/sql/009_rename_community_to_feedback.sql (manuell via psql, in Drizzle-Schema beider Services nachgezogen). - mana-auth/me.ts: PATCH /api/v1/me/profile akzeptiert jetzt feedbackShowRealName und gibt es im Response zurück. - mana-analytics: feedback.ts liest authUsers.feedbackShowRealName / feedbackKarma, redact() + Karma-Increment + Tests entsprechend. - Frontend: CommunitySection.svelte → FeedbackIdentitySection.svelte (Datei umbenannt, Property-Namen + Toast-Texte aktualisiert, HeartHalf-Icon, "Feedback-Identität" als Title). - searchIndex.ts: CategoryId 'community' → 'feedback', anchor 'community-identity' → 'feedback-identity'. - i18n (5 locales): settings.categories.community → .feedback, settings.search.community_* → feedback_*. Labels DE/EN/FR/IT/ES jeweils auf "Feedback" + "im Feedback-Feed" angepasst. 38/38 Integration-Tests grün, validate:i18n-parity sauber, svelte-check 0. BREAKING (intern, nicht live): Frontend, das gegen die alten Spalten- / Property-Namen aus dem PATCH-Response geht, fällt jetzt um. Kein Production-Risiko da Hub noch nicht öffentlich. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6f83fba66a
commit
941df57f77
16 changed files with 544 additions and 209 deletions
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Read-only cross-schema view of auth.users for the public-community
|
||||
* Read-only cross-schema view of auth.users for the public-feedback
|
||||
* hub. mana-auth owns the table; we JOIN it from mana-analytics to
|
||||
* enrich feed responses with the post-author's real-name opt-in and
|
||||
* karma score. We never INSERT/UPDATE/DELETE here — that's
|
||||
|
|
@ -17,6 +17,6 @@ const authSchema = pgSchema('auth');
|
|||
export const authUsers = authSchema.table('users', {
|
||||
id: text('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
communityShowRealName: boolean('community_show_real_name').default(false).notNull(),
|
||||
communityKarma: integer('community_karma').default(0).notNull(),
|
||||
feedbackShowRealName: boolean('feedback_show_real_name').default(false).notNull(),
|
||||
feedbackKarma: integer('feedback_karma').default(0).notNull(),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* Integration tests for the cross-schema karma flow.
|
||||
*
|
||||
* Karma lives on auth.users.community_karma; mana-analytics increments
|
||||
* Karma lives on auth.users.feedback_karma; mana-analytics increments
|
||||
* it inside toggleReaction. Tests verify the SQL path, the self-react
|
||||
* skip, and the floor-at-zero clamp.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Privacy-boundary tests für die `redact()`-Funktion.
|
||||
*
|
||||
* Kritisch: anonymous public endpoint darf NIE einen Klarnamen
|
||||
* ausliefern, auch wenn der User-Account `communityShowRealName=true`
|
||||
* ausliefern, auch wenn der User-Account `feedbackShowRealName=true`
|
||||
* gesetzt hat. Diese Tests sind das Sicherheitsnetz für die ›Public
|
||||
* bleibt anonym‹-Garantie der Community-Surface.
|
||||
*/
|
||||
|
|
@ -35,14 +35,14 @@ const baseFeedback = {
|
|||
|
||||
const optedInAuthor = {
|
||||
name: 'Till Schäfer',
|
||||
communityShowRealName: true,
|
||||
communityKarma: 47,
|
||||
feedbackShowRealName: true,
|
||||
feedbackKarma: 47,
|
||||
};
|
||||
|
||||
const optedOutAuthor = {
|
||||
name: 'Till Schäfer',
|
||||
communityShowRealName: false,
|
||||
communityKarma: 47,
|
||||
feedbackShowRealName: false,
|
||||
feedbackKarma: 47,
|
||||
};
|
||||
|
||||
describe('redact (privacy-boundary)', () => {
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ export type PublicFeedbackItem = {
|
|||
/** Author's community karma (public, drives tier-badge). */
|
||||
karma: number;
|
||||
/** Real name, only present when:
|
||||
* - the post-author opted in via communityShowRealName=true, AND
|
||||
* - the post-author opted in via feedbackShowRealName=true, AND
|
||||
* - the response is going to an authenticated caller (the
|
||||
* anonymous /public endpoint always strips this).
|
||||
*/
|
||||
|
|
@ -88,8 +88,8 @@ export type PublicFeedbackItem = {
|
|||
type FeedbackRow = typeof userFeedback.$inferSelect;
|
||||
type AuthUserRow = {
|
||||
name: string;
|
||||
communityShowRealName: boolean;
|
||||
communityKarma: number;
|
||||
feedbackShowRealName: boolean;
|
||||
feedbackKarma: number;
|
||||
} | null;
|
||||
|
||||
export class FeedbackService {
|
||||
|
|
@ -301,7 +301,7 @@ export class FeedbackService {
|
|||
|
||||
const items = rows.map((r) => redact(r.feedback, r.author, { includeRealName: false }));
|
||||
const displayName = items[0]?.displayName ?? null;
|
||||
const karma = rows[0]?.author?.communityKarma ?? 0;
|
||||
const karma = rows[0]?.author?.feedbackKarma ?? 0;
|
||||
|
||||
return { displayHash, displayName, karma, items };
|
||||
}
|
||||
|
|
@ -327,8 +327,8 @@ export class FeedbackService {
|
|||
private authorSelection() {
|
||||
return {
|
||||
name: authUsers.name,
|
||||
communityShowRealName: authUsers.communityShowRealName,
|
||||
communityKarma: authUsers.communityKarma,
|
||||
feedbackShowRealName: authUsers.feedbackShowRealName,
|
||||
feedbackKarma: authUsers.feedbackKarma,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -442,7 +442,7 @@ export class FeedbackService {
|
|||
await this.db
|
||||
.update(authUsers)
|
||||
.set({
|
||||
communityKarma: sql`GREATEST(${authUsers.communityKarma} + ${delta}, 0)`,
|
||||
feedbackKarma: sql`GREATEST(${authUsers.feedbackKarma} + ${delta}, 0)`,
|
||||
})
|
||||
.where(eq(authUsers.id, item.authorId));
|
||||
}
|
||||
|
|
@ -767,9 +767,9 @@ function redact(
|
|||
adminResponse: row.adminResponse,
|
||||
createdAt: row.createdAt,
|
||||
updatedAt: row.updatedAt,
|
||||
karma: author?.communityKarma ?? 0,
|
||||
karma: author?.feedbackKarma ?? 0,
|
||||
};
|
||||
if (includeReal && author?.communityShowRealName && author.name) {
|
||||
if (includeReal && author?.feedbackShowRealName && author.name) {
|
||||
item.realName = author.name;
|
||||
}
|
||||
return item;
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ let seededIds = new Set<string>();
|
|||
*/
|
||||
export async function seedUser(
|
||||
db: TestDb,
|
||||
overrides: Partial<{ name: string; communityShowRealName: boolean; communityKarma: number }> = {}
|
||||
overrides: Partial<{ name: string; feedbackShowRealName: boolean; feedbackKarma: number }> = {}
|
||||
): Promise<SeededUser> {
|
||||
const id = `test-${randomUUID()}`;
|
||||
const email = `${id}@test.local`;
|
||||
|
|
@ -57,13 +57,13 @@ export async function seedUser(
|
|||
// model only declares the columns mana-analytics READS — auth.users
|
||||
// has additional NOT NULL columns (email, etc.) we'd otherwise miss.
|
||||
await db.execute(sql`
|
||||
INSERT INTO auth.users (id, email, name, community_show_real_name, community_karma)
|
||||
INSERT INTO auth.users (id, email, name, feedback_show_real_name, feedback_karma)
|
||||
VALUES (
|
||||
${id},
|
||||
${email},
|
||||
${name},
|
||||
${overrides.communityShowRealName ?? false},
|
||||
${overrides.communityKarma ?? 0}
|
||||
${overrides.feedbackShowRealName ?? false},
|
||||
${overrides.feedbackKarma ?? 0}
|
||||
)
|
||||
ON CONFLICT (id) DO NOTHING
|
||||
`);
|
||||
|
|
@ -71,10 +71,10 @@ export async function seedUser(
|
|||
return { id, email, name };
|
||||
}
|
||||
|
||||
/** Read auth.users.community_karma for a test user. */
|
||||
/** Read auth.users.feedback_karma for a test user. */
|
||||
export async function getKarma(db: TestDb, userId: string): Promise<number> {
|
||||
const [row] = await db
|
||||
.select({ karma: authUsers.communityKarma })
|
||||
.select({ karma: authUsers.feedbackKarma })
|
||||
.from(authUsers)
|
||||
.where(eq(authUsers.id, userId))
|
||||
.limit(1);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue