Files
cim_summary/backend/src/models/UserModel.ts
Jon 185c780486
Some checks failed
CI/CD Pipeline / Backend - Lint & Test (push) Has been cancelled
CI/CD Pipeline / Frontend - Lint & Test (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Build Backend (push) Has been cancelled
CI/CD Pipeline / Build Frontend (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / Performance Tests (push) Has been cancelled
CI/CD Pipeline / Dependency Updates (push) Has been cancelled
🚀 Update to Claude 3.7 latest and fix LLM processing issues
- Updated Anthropic API to latest version (2024-01-01)
- Set Claude 3.7 Sonnet Latest as primary model
- Removed deprecated Opus 3.5 references
- Fixed LLM response validation and JSON parsing
- Improved error handling and logging
- Updated model configurations and pricing
- Enhanced document processing reliability
- Fixed TypeScript type issues
- Updated environment configuration
2025-08-17 17:31:56 -04:00

391 lines
10 KiB
TypeScript

import { getSupabaseServiceClient } from '../config/supabase';
import { User, CreateUserInput } from './types';
import logger from '../utils/logger';
import { redisCacheService } from '../services/redisCacheService';
export class UserModel {
/**
* Create a new user
*/
static async create(userData: CreateUserInput): Promise<User> {
const { email, name, password, role = 'user' } = userData;
const supabase = getSupabaseServiceClient();
try {
const { data, error } = await supabase
.from('users')
.insert({
email,
name,
password_hash: password, // Note: In production, this should be hashed
role
})
.select()
.single();
if (error) {
logger.error('Error creating user:', error);
throw error;
}
logger.info(`Created user: ${email}`);
return data;
} catch (error) {
logger.error('Error creating user:', error);
throw error;
}
}
/**
* Find user by ID
*/
static async findById(id: string): Promise<User | null> {
const supabase = getSupabaseServiceClient();
try {
const { data, error } = await supabase
.from('users')
.select('*')
.eq('id', id)
.eq('is_active', true)
.single();
if (error) {
if (error.code === 'PGRST116') {
return null; // No rows returned
}
logger.error('Error finding user by ID:', error);
throw error;
}
return data;
} catch (error) {
logger.error('Error finding user by ID:', error);
throw error;
}
}
/**
* Find user by email
*/
static async findByEmail(email: string): Promise<User | null> {
const supabase = getSupabaseServiceClient();
try {
const { data, error } = await supabase
.from('users')
.select('*')
.eq('email', email)
.eq('is_active', true)
.single();
if (error) {
if (error.code === 'PGRST116') {
return null; // No rows returned
}
logger.error('Error finding user by email:', error);
throw error;
}
return data;
} catch (error) {
logger.error('Error finding user by email:', error);
throw error;
}
}
/**
* Get all users (for admin)
*/
static async findAll(limit = 100, offset = 0): Promise<User[]> {
const supabase = getSupabaseServiceClient();
try {
const { data, error } = await supabase
.from('users')
.select('*')
.eq('is_active', true)
.order('created_at', { ascending: false })
.range(offset, offset + limit - 1);
if (error) {
logger.error('Error finding all users:', error);
throw error;
}
return data || [];
} catch (error) {
logger.error('Error finding all users:', error);
throw error;
}
}
/**
* Update user
*/
static async update(id: string, updates: Partial<User>): Promise<User | null> {
const supabase = getSupabaseServiceClient();
try {
const { data, error } = await supabase
.from('users')
.update({
...updates,
updated_at: new Date().toISOString()
})
.eq('id', id)
.select()
.single();
if (error) {
if (error.code === 'PGRST116') {
return null; // No rows returned
}
logger.error('Error updating user:', error);
throw error;
}
return data;
} catch (error) {
logger.error('Error updating user:', error);
throw error;
}
}
/**
* Update last login
*/
static async updateLastLogin(id: string): Promise<void> {
const supabase = getSupabaseServiceClient();
try {
const { error } = await supabase
.from('users')
.update({
last_login: new Date().toISOString(),
updated_at: new Date().toISOString()
})
.eq('id', id);
if (error) {
logger.error('Error updating last login:', error);
throw error;
}
} catch (error) {
logger.error('Error updating last login:', error);
throw error;
}
}
/**
* Delete user (soft delete)
*/
static async delete(id: string): Promise<boolean> {
const supabase = getSupabaseServiceClient();
try {
const { error } = await supabase
.from('users')
.update({
is_active: false,
updated_at: new Date().toISOString()
})
.eq('id', id);
if (error) {
logger.error('Error deleting user:', error);
throw error;
}
return true;
} catch (error) {
logger.error('Error deleting user:', error);
throw error;
}
}
/**
* Count users
*/
static async count(): Promise<number> {
const supabase = getSupabaseServiceClient();
try {
const { count, error } = await supabase
.from('users')
.select('*', { count: 'exact', head: true })
.eq('is_active', true);
if (error) {
logger.error('Error counting users:', error);
throw error;
}
return count || 0;
} catch (error) {
logger.error('Error counting users:', error);
throw error;
}
}
/**
* Check if email exists
*/
static async emailExists(email: string): Promise<boolean> {
const supabase = getSupabaseServiceClient();
try {
const { data, error } = await supabase
.from('users')
.select('id')
.eq('email', email)
.eq('is_active', true)
.limit(1);
if (error) {
logger.error('Error checking if email exists:', error);
throw error;
}
return (data && data.length > 0);
} catch (error) {
logger.error('Error checking if email exists:', error);
throw error;
}
}
/**
* Get all users (admin only)
*/
static async getAllUsers(): Promise<any[]> {
const supabase = getSupabaseServiceClient();
try {
const { data, error } = await supabase
.from('users')
.select('*')
.eq('is_active', true)
.order('created_at', { ascending: false });
if (error) {
logger.error('Error getting all users:', error);
throw error;
}
return data || [];
} catch (error) {
logger.error('Error getting all users:', error);
throw error;
}
}
/**
* Get user activity statistics (admin only) with Redis caching
*/
static async getUserActivityStats(): Promise<any[]> {
const cacheKey = 'user_activity_stats';
try {
// Try to get from cache first
const cachedData = await redisCacheService.get<any[]>(cacheKey, { prefix: 'analytics' });
if (cachedData) {
logger.info('User activity stats retrieved from cache');
return cachedData;
}
const supabase = getSupabaseServiceClient();
// Get users with their document counts and last activity
const { data, error } = await supabase
.from('users')
.select(`
id,
email,
name,
created_at,
last_login,
is_active
`)
.eq('is_active', true)
.order('last_login', { ascending: false });
if (error) {
logger.error('Error getting user activity stats:', error);
throw error;
}
// Get document statistics for each user
const usersWithStats = await Promise.all(
(data || []).map(async (user) => {
try {
// Get document count for user
const { data: docData, error: docError } = await supabase
.from('documents')
.select('id, processing_time_ms, created_at')
.eq('user_id', user.id)
.eq('deleted_at', null);
if (docError) {
logger.error('Error getting document stats for user:', docError);
return {
userId: user.id,
email: user.email,
name: user.name,
loginCount: 1,
lastLogin: user.last_login,
documentsProcessed: 0,
totalProcessingTime: 0,
averageProcessingTime: 0
};
}
const documents = docData || [];
const totalProcessingTime = documents.reduce((sum, doc) => sum + (doc.processing_time_ms || 0), 0);
const averageProcessingTime = documents.length > 0 ? totalProcessingTime / documents.length : 0;
// Get login count from processing jobs (as a proxy for activity)
const { data: jobData, error: jobError } = await supabase
.from('processing_jobs')
.select('id')
.eq('user_id', user.id);
const loginCount = jobError ? 1 : (jobData?.length || 1);
return {
userId: user.id,
email: user.email,
name: user.name,
loginCount,
lastLogin: user.last_login,
documentsProcessed: documents.length,
totalProcessingTime,
averageProcessingTime: Math.round(averageProcessingTime)
};
} catch (error) {
logger.error('Error calculating stats for user:', error);
return {
userId: user.id,
email: user.email,
name: user.name,
loginCount: 1,
lastLogin: user.last_login,
documentsProcessed: 0,
totalProcessingTime: 0,
averageProcessingTime: 0
};
}
})
);
// Cache the results for 30 minutes
await redisCacheService.set(cacheKey, usersWithStats, { ttl: 1800, prefix: 'analytics' });
logger.info('User activity stats cached successfully');
return usersWithStats;
} catch (error) {
logger.error('Error getting user activity stats:', error);
throw error;
}
}
}