template-saas/apps/backend/src/modules/ai/entities/ai-usage.entity.ts
rckrdmrd 40d57f8124 feat: Add AI Integration, Notifications UI and Settings Page
FASE 1: Notifications UI
- Add NotificationBell, NotificationDrawer, NotificationItem components
- Integrate notification bell in DashboardLayout header
- Real-time unread count with polling

FASE 2: AI Integration Backend
- Add AI module with OpenRouter client
- Endpoints: POST /ai/chat, GET /ai/models, GET/PATCH /ai/config
- GET /ai/usage, GET /ai/usage/current, GET /ai/health
- Database: schema ai with configs and usage tables
- Token tracking and cost calculation

FASE 3: Settings Page Refactor
- Restructure with tabs navigation
- GeneralSettings: profile, organization, appearance
- NotificationSettings: channels and categories toggles
- SecuritySettings: password change, 2FA placeholder, sessions

Files created: 25+
Endpoints added: 7
Story Points completed: 21

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 07:04:29 -06:00

91 lines
2.1 KiB
TypeScript

import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
} from 'typeorm';
import { AIProvider } from './ai-config.entity';
export enum AIModelType {
CHAT = 'chat',
COMPLETION = 'completion',
EMBEDDING = 'embedding',
IMAGE = 'image',
}
export enum UsageStatus {
PENDING = 'pending',
COMPLETED = 'completed',
FAILED = 'failed',
CANCELLED = 'cancelled',
}
@Entity({ name: 'usage', schema: 'ai' })
export class AIUsage {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'uuid' })
tenant_id: string;
@Column({ type: 'uuid' })
user_id: string;
@Column({ type: 'enum', enum: AIProvider })
provider: AIProvider;
@Column({ type: 'varchar', length: 100 })
model: string;
@Column({ type: 'enum', enum: AIModelType, default: AIModelType.CHAT })
model_type: AIModelType;
@Column({ type: 'enum', enum: UsageStatus, default: UsageStatus.PENDING })
status: UsageStatus;
@Column({ type: 'int', default: 0 })
input_tokens: number;
@Column({ type: 'int', default: 0 })
output_tokens: number;
// total_tokens is computed in DB, but we can add it for convenience
get total_tokens(): number {
return this.input_tokens + this.output_tokens;
}
@Column({ type: 'numeric', precision: 12, scale: 6, default: 0 })
cost_input: number;
@Column({ type: 'numeric', precision: 12, scale: 6, default: 0 })
cost_output: number;
get cost_total(): number {
return Number(this.cost_input) + Number(this.cost_output);
}
@Column({ type: 'int', nullable: true })
latency_ms: number;
@Column({ type: 'timestamptz', default: () => 'NOW()' })
started_at: Date;
@Column({ type: 'timestamptz', nullable: true })
completed_at: Date;
@Column({ type: 'varchar', length: 100, nullable: true })
request_id: string;
@Column({ type: 'varchar', length: 50, nullable: true })
endpoint: string;
@Column({ type: 'text', nullable: true })
error_message: string;
@Column({ type: 'jsonb', default: {} })
metadata: Record<string, any>;
@CreateDateColumn({ type: 'timestamptz' })
created_at: Date;
}