managarten/services/mana-analytics/src/db/schema/feedback.ts
Till JS 753c685ef7 feat(services): create mana-analytics, remove feedback/analytics/ai from auth
Extract feedback, analytics, and AI modules from mana-core-auth into
standalone mana-analytics service (Hono + Bun, Port 3064).

New service (services/mana-analytics/):
- User feedback CRUD with voting
- AI-powered feedback title generation via mana-llm
- Simplified from DuckDB analytics to pure PostgreSQL
- ~550 LOC

Removed from mana-core-auth:
- feedback/ module (6 files)
- analytics/ module (4 files)
- ai/ module (3 files)
- db/schema/feedback.schema.ts

mana-core-auth now contains ONLY pure auth:
- Better Auth (JWT, Sessions, 2FA, Passkeys, OIDC, Magic Links)
- Organizations/Guilds (membership management)
- API Keys, Security, Me (GDPR), Health, Metrics
- Ready for Phase 5: Hono rewrite

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 02:29:24 +01:00

74 lines
2 KiB
TypeScript

import {
pgSchema,
uuid,
text,
timestamp,
integer,
boolean,
jsonb,
index,
unique,
pgEnum,
} from 'drizzle-orm/pg-core';
export const feedbackSchema = pgSchema('feedback');
export const feedbackCategoryEnum = pgEnum('feedback_category', [
'bug',
'feature',
'improvement',
'question',
'praise',
'other',
]);
export const feedbackStatusEnum = pgEnum('feedback_status', [
'new',
'reviewed',
'planned',
'in_progress',
'done',
'rejected',
]);
export const userFeedback = feedbackSchema.table(
'user_feedback',
{
id: uuid('id').primaryKey().defaultRandom(),
userId: text('user_id').notNull(),
appId: text('app_id').notNull(),
title: text('title'),
feedbackText: text('feedback_text').notNull(),
category: feedbackCategoryEnum('category').default('other').notNull(),
status: feedbackStatusEnum('status').default('new').notNull(),
isPublic: boolean('is_public').default(true).notNull(),
adminResponse: text('admin_response'),
voteCount: integer('vote_count').default(0).notNull(),
deviceInfo: jsonb('device_info'),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
},
(table) => ({
userIdIdx: index('feedback_user_id_idx').on(table.userId),
appIdIdx: index('feedback_app_id_idx').on(table.appId),
statusIdx: index('feedback_status_idx').on(table.status),
})
);
export const feedbackVotes = feedbackSchema.table(
'feedback_votes',
{
id: uuid('id').primaryKey().defaultRandom(),
feedbackId: uuid('feedback_id')
.notNull()
.references(() => userFeedback.id, { onDelete: 'cascade' }),
userId: text('user_id').notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
},
(table) => ({
feedbackUserUnique: unique('feedback_votes_unique').on(table.feedbackId, table.userId),
})
);
export type Feedback = typeof userFeedback.$inferSelect;
export type FeedbackVote = typeof feedbackVotes.$inferSelect;