mirror of
https://github.com/Dvorinka/Trackeep.git
synced 2026-07-29 05:53:50 +00:00
- Replace Redis with DragonflyDB for better performance and memory efficiency - Remove redundant environment variables (POSTGRES_*, ENCRYPTION_KEY, OAUTH_SERVICE_URL) - Consolidate database configuration to use single DB_* variables - Use JWT_SECRET for both JWT tokens and encryption - Remove PORT variable redundancy, use BACKEND_PORT consistently - Clean up docker-compose configurations for dev/prod consistency - Add DragonflyDB configuration with optimized memory usage - Remove redis.conf as it's no longer needed - Update health checks to use Redis-compatible CLI for DragonflyDB - Add missing VITE_API_URL to production frontend - Fix GitHub Actions to use correct go.sum path - Clean up development directories and unused files
1939 lines
61 KiB
TypeScript
1939 lines
61 KiB
TypeScript
// Demo mode API interceptor to provide mock data instead of making real API calls
|
|
|
|
import { hasAnyCredentials, isBackendAvailable, isSearchAvailable } from './credentials';
|
|
|
|
// Check if demo mode is enabled via environment variable
|
|
export const isEnvDemoMode = (): boolean => {
|
|
// First check runtime environment variable (for Docker) - this should take priority
|
|
const runtimeResult = (window as any).ENV?.VITE_DEMO_MODE === 'true';
|
|
|
|
// Check window.importMetaEnv as well
|
|
const importMetaEnvResult = (window as any).importMetaEnv?.VITE_DEMO_MODE === 'true';
|
|
|
|
// Then check build-time environment variable as fallback
|
|
const buildTimeResult = import.meta.env.VITE_DEMO_MODE === 'true';
|
|
|
|
const result = runtimeResult || importMetaEnvResult || buildTimeResult;
|
|
return result;
|
|
};
|
|
|
|
// Check if demo mode is active (environment variable only)
|
|
export const isDemoMode = (): boolean => {
|
|
// Only check environment variable - no localStorage persistence
|
|
return isEnvDemoMode();
|
|
};
|
|
|
|
// Check if we should use real APIs even in demo mode
|
|
export const shouldUseRealAPIs = (): boolean => {
|
|
const hasCredentials = hasAnyCredentials();
|
|
const hasBackend = shouldUseRealBackend();
|
|
const result = hasCredentials || hasBackend;
|
|
return result;
|
|
};
|
|
|
|
// Check if we should use real backend API
|
|
export const shouldUseRealBackend = (): boolean => {
|
|
return isBackendAvailable();
|
|
};
|
|
|
|
// Check if we should use real search APIs
|
|
export const shouldUseRealSearch = (): boolean => {
|
|
return isSearchAvailable();
|
|
};
|
|
|
|
// Clear demo mode from localStorage
|
|
export const clearDemoMode = (): void => {
|
|
localStorage.removeItem('demoMode');
|
|
// Only clear demo tokens, not legitimate user tokens
|
|
const token = localStorage.getItem('token');
|
|
|
|
// Only clear if they look like demo tokens (contain 'demo-token')
|
|
if (token && token.includes('demo-token')) {
|
|
localStorage.removeItem('token');
|
|
localStorage.removeItem('user');
|
|
localStorage.removeItem('trackeep_token');
|
|
localStorage.removeItem('trackeep_user');
|
|
}
|
|
};
|
|
|
|
// Set demo mode (no-op - environment variable only)
|
|
export const setDemoMode = (): void => {
|
|
// Demo mode is controlled by environment variable only
|
|
// No localStorage persistence
|
|
};
|
|
|
|
// Mock data generators
|
|
const generateMockStats = () => ({
|
|
total_tasks: 42,
|
|
completed_tasks: 28,
|
|
total_bookmarks: 156,
|
|
total_notes: 89,
|
|
total_files: 234,
|
|
total_time_tracked: 125000, // seconds
|
|
recent_activity: [
|
|
{ type: 'task', action: 'completed', title: 'Complete project documentation', timestamp: new Date(Date.now() - 3600000).toISOString() },
|
|
{ type: 'bookmark', action: 'added', title: 'SolidJS Documentation', timestamp: new Date(Date.now() - 7200000).toISOString() },
|
|
{ type: 'note', action: 'created', title: 'Meeting notes - Q1 planning', timestamp: new Date(Date.now() - 10800000).toISOString() },
|
|
{ type: 'file', action: 'uploaded', title: 'project-roadmap.pdf', timestamp: new Date(Date.now() - 14400000).toISOString() },
|
|
],
|
|
totalTimeTracked: 125000,
|
|
averageProductivity: 78,
|
|
recentProjects: [
|
|
{ name: 'Trackeep Frontend', progress: 85, status: 'active' },
|
|
{ name: 'API Documentation', progress: 60, status: 'active' },
|
|
{ name: 'Mobile App', progress: 30, status: 'planning' }
|
|
],
|
|
topCategories: [
|
|
{ name: 'Development', count: 45, color: '#3b82f6' },
|
|
{ name: 'Documentation', count: 32, color: '#10b981' },
|
|
{ name: 'Design', count: 28, color: '#f59e0b' },
|
|
{ name: 'Research', count: 21, color: '#8b5cf6' },
|
|
{ name: 'Testing', count: 18, color: '#ef4444' }
|
|
],
|
|
weeklyActivity: [12, 8, 15, 6, 9, 14, 3]
|
|
});
|
|
|
|
const generateMockGitHubRepos = () => [
|
|
{
|
|
id: 1,
|
|
name: 'trackeep',
|
|
full_name: 'tdvorak/trackeep',
|
|
description: 'Your Self-Hosted Productivity Hub',
|
|
private: false,
|
|
stargazers_count: 245,
|
|
forks_count: 65,
|
|
watchers_count: 43,
|
|
language: 'Go',
|
|
updated_at: new Date().toISOString(),
|
|
html_url: 'https://github.com/tdvorak/trackeep',
|
|
created_at: new Date(Date.now() - 365 * 24 * 60 * 60 * 1000).toISOString(),
|
|
size: 1250,
|
|
open_issues_count: 12,
|
|
default_branch: 'main'
|
|
},
|
|
{
|
|
id: 2,
|
|
name: 'solidjs-components',
|
|
full_name: 'tdvorak/solidjs-components',
|
|
description: 'Reusable SolidJS components library',
|
|
private: false,
|
|
stargazers_count: 89,
|
|
forks_count: 12,
|
|
watchers_count: 8,
|
|
language: 'TypeScript',
|
|
updated_at: new Date(Date.now() - 86400000).toISOString(),
|
|
html_url: 'https://github.com/tdvorak/solidjs-components',
|
|
created_at: new Date(Date.now() - 180 * 24 * 60 * 60 * 1000).toISOString(),
|
|
size: 450,
|
|
open_issues_count: 3,
|
|
default_branch: 'main'
|
|
},
|
|
{
|
|
id: 3,
|
|
name: 'docker-homelab',
|
|
full_name: 'tdvorak/docker-homelab',
|
|
description: 'Complete Docker setup for home lab environment',
|
|
private: false,
|
|
stargazers_count: 156,
|
|
forks_count: 34,
|
|
watchers_count: 21,
|
|
language: 'Dockerfile',
|
|
updated_at: new Date(Date.now() - 2 * 86400000).toISOString(),
|
|
html_url: 'https://github.com/tdvorak/docker-homelab',
|
|
created_at: new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString(),
|
|
size: 890,
|
|
open_issues_count: 7,
|
|
default_branch: 'main'
|
|
},
|
|
{
|
|
id: 4,
|
|
name: 'network-tools',
|
|
full_name: 'tdvorak/network-tools',
|
|
description: 'Collection of networking utilities and scripts',
|
|
private: true,
|
|
stargazers_count: 45,
|
|
forks_count: 8,
|
|
watchers_count: 5,
|
|
language: 'Python',
|
|
updated_at: new Date(Date.now() - 3 * 86400000).toISOString(),
|
|
html_url: 'https://github.com/tdvorak/network-tools',
|
|
created_at: new Date(Date.now() - 60 * 24 * 60 * 60 * 1000).toISOString(),
|
|
size: 234,
|
|
open_issues_count: 2,
|
|
default_branch: 'develop'
|
|
}
|
|
];
|
|
|
|
const generateMockTimeEntries = () => [
|
|
{
|
|
id: 1,
|
|
description: 'Working on Trackeep frontend',
|
|
start_time: new Date(Date.now() - 7200000).toISOString(),
|
|
end_time: new Date(Date.now() - 3600000).toISOString(),
|
|
duration: 3600,
|
|
billable: true,
|
|
hourly_rate: 75,
|
|
task_id: 1,
|
|
created_at: new Date(Date.now() - 3600000).toISOString()
|
|
},
|
|
{
|
|
id: 2,
|
|
description: 'Meeting with team',
|
|
start_time: new Date(Date.now() - 14400000).toISOString(),
|
|
end_time: new Date(Date.now() - 12600000).toISOString(),
|
|
duration: 1800,
|
|
billable: false,
|
|
hourly_rate: 0,
|
|
task_id: null,
|
|
created_at: new Date(Date.now() - 12600000).toISOString()
|
|
}
|
|
];
|
|
|
|
const generateMockVideoBookmarks = () => [
|
|
{
|
|
id: 1,
|
|
video_id: 'dQw4w9WgXcQ',
|
|
title: 'Never Gonna Give You Up',
|
|
description: 'Classic music video from Rick Astley',
|
|
channel: 'Rick Astley',
|
|
thumbnail: 'https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg',
|
|
duration: '3:33',
|
|
publishedAt: '2009-10-25',
|
|
url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
|
|
tags: [
|
|
{ id: 1, name: 'music', color: '#ff6b6b' },
|
|
{ id: 2, name: 'classic', color: '#4ecdc4' },
|
|
{ id: 3, name: '80s', color: '#45b7d1' }
|
|
],
|
|
created_at: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString()
|
|
},
|
|
{
|
|
id: 2,
|
|
video_id: 'jNQXAC9IVRw',
|
|
title: 'How to Build a REST API with Go',
|
|
description: 'Complete tutorial on building RESTful APIs using Go and Gin framework',
|
|
channel: 'Tech with Tim',
|
|
thumbnail: 'https://img.youtube.com/vi/jNQXAC9IVRw/maxresdefault.jpg',
|
|
duration: '45:20',
|
|
publishedAt: '2024-01-15',
|
|
url: 'https://www.youtube.com/watch?v=jNQXAC9IVRw',
|
|
tags: [
|
|
{ id: 4, name: 'programming', color: '#9b59b6' },
|
|
{ id: 5, name: 'golang', color: '#3498db' },
|
|
{ id: 6, name: 'tutorial', color: '#2ecc71' }
|
|
],
|
|
created_at: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString()
|
|
},
|
|
{
|
|
id: 3,
|
|
video_id: '8S2UDfJ9R8A',
|
|
title: 'SolidJS vs React: A Comprehensive Comparison',
|
|
description: 'Deep dive into the differences between SolidJS and React frameworks',
|
|
channel: 'Frontend Masters',
|
|
thumbnail: 'https://img.youtube.com/vi/8S2UDfJ9R8A/maxresdefault.jpg',
|
|
duration: '28:15',
|
|
publishedAt: '2024-02-01',
|
|
url: 'https://www.youtube.com/watch?v=8S2UDfJ9R8A',
|
|
tags: [
|
|
{ id: 7, name: 'javascript', color: '#f39c12' },
|
|
{ id: 8, name: 'frontend', color: '#e74c3c' },
|
|
{ id: 9, name: 'framework', color: '#27ae60' }
|
|
],
|
|
created_at: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString()
|
|
},
|
|
{
|
|
id: 4,
|
|
video_id: '5qap5aO4i9A',
|
|
title: 'Docker Networking Explained',
|
|
description: 'Understanding Docker networking concepts and best practices',
|
|
channel: 'DevOps Toolkit',
|
|
thumbnail: 'https://img.youtube.com/vi/5qap5aO4i9A/maxresdefault.jpg',
|
|
duration: '32:45',
|
|
publishedAt: '2024-01-20',
|
|
url: 'https://www.youtube.com/watch?v=5qap5aO4i9A',
|
|
tags: [
|
|
{ id: 10, name: 'docker', color: '#0db7ed' },
|
|
{ id: 11, name: 'devops', color: '#ff5722' },
|
|
{ id: 12, name: 'networking', color: '#4caf50' }
|
|
],
|
|
created_at: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString()
|
|
},
|
|
{
|
|
id: 5,
|
|
video_id: 'LXb3EKWsInQ',
|
|
title: 'Productivity Tips for Developers',
|
|
description: 'Essential productivity tips and tools for software developers',
|
|
channel: 'Coding Career',
|
|
thumbnail: 'https://img.youtube.com/vi/LXb3EKWsInQ/maxresdefault.jpg',
|
|
duration: '18:30',
|
|
publishedAt: '2024-02-10',
|
|
url: 'https://www.youtube.com/watch?v=LXb3EKWsInQ',
|
|
tags: [
|
|
{ id: 13, name: 'productivity', color: '#ff9800' },
|
|
{ id: 14, name: 'development', color: '#607d8b' },
|
|
{ id: 15, name: 'tips', color: '#795548' }
|
|
],
|
|
created_at: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString()
|
|
}
|
|
];
|
|
|
|
const generateMockTeams = () => [
|
|
{
|
|
id: 1,
|
|
name: 'Trackeep Workspace',
|
|
description: 'Main workspace for Trackeep development',
|
|
created_at: new Date(Date.now() - 365 * 24 * 60 * 60 * 1000).toISOString(),
|
|
updated_at: new Date().toISOString()
|
|
}
|
|
];
|
|
|
|
const generateMockMembers = () => [
|
|
{
|
|
user_id: 1,
|
|
user: {
|
|
id: 1,
|
|
username: 'demo',
|
|
email: '[email protected]',
|
|
full_name: 'Demo User'
|
|
},
|
|
role: 'owner',
|
|
joined_at: new Date(Date.now() - 365 * 24 * 60 * 60 * 1000).toISOString()
|
|
},
|
|
{
|
|
user_id: 2,
|
|
user: {
|
|
id: 2,
|
|
username: 'alice',
|
|
email: '[email protected]',
|
|
full_name: 'Alice Johnson'
|
|
},
|
|
role: 'admin',
|
|
joined_at: new Date(Date.now() - 180 * 24 * 60 * 60 * 1000).toISOString()
|
|
},
|
|
{
|
|
user_id: 3,
|
|
user: {
|
|
id: 3,
|
|
username: 'bob',
|
|
email: '[email protected]',
|
|
full_name: 'Bob Smith'
|
|
},
|
|
role: 'member',
|
|
joined_at: new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString()
|
|
},
|
|
{
|
|
user_id: 4,
|
|
user: {
|
|
id: 4,
|
|
username: 'carol',
|
|
email: '[email protected]',
|
|
full_name: 'Carol Davis'
|
|
},
|
|
role: 'member',
|
|
joined_at: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString()
|
|
}
|
|
];
|
|
|
|
const generateMockLearningPaths = () => [
|
|
{
|
|
id: 1,
|
|
title: 'SolidJS Mastery',
|
|
description: 'Complete guide to SolidJS framework',
|
|
category: 'frontend',
|
|
difficulty: 'intermediate',
|
|
estimated_hours: 20,
|
|
progress: 65,
|
|
created_at: new Date().toISOString(),
|
|
updated_at: new Date().toISOString()
|
|
},
|
|
{
|
|
id: 2,
|
|
title: 'Go Backend Development',
|
|
description: 'Build scalable backend with Go',
|
|
category: 'backend',
|
|
difficulty: 'advanced',
|
|
estimated_hours: 40,
|
|
progress: 30,
|
|
created_at: new Date().toISOString(),
|
|
updated_at: new Date().toISOString()
|
|
}
|
|
];
|
|
|
|
const generateMockChatSessions = () => [
|
|
{
|
|
id: 1,
|
|
title: 'Project Planning Discussion',
|
|
created_at: new Date(Date.now() - 86400000).toISOString(),
|
|
updated_at: new Date(Date.now() - 3600000).toISOString(),
|
|
message_count: 15
|
|
},
|
|
{
|
|
id: 2,
|
|
title: 'Code Review Help',
|
|
created_at: new Date(Date.now() - 172800000).toISOString(),
|
|
updated_at: new Date(Date.now() - 7200000).toISOString(),
|
|
message_count: 8
|
|
}
|
|
];
|
|
|
|
const generateMockRemovedItems = () => [
|
|
{
|
|
id: '1',
|
|
name: 'Old Project Documentation.pdf',
|
|
type: 'pdf',
|
|
removedAt: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toLocaleDateString(),
|
|
removedBy: 'Demo User',
|
|
size: '2.4 MB',
|
|
path: '/documents/projects/',
|
|
daysInTrash: 5
|
|
},
|
|
{
|
|
id: '2',
|
|
name: 'Backup Database.sql',
|
|
type: 'sql',
|
|
removedAt: new Date(Date.now() - 12 * 24 * 60 * 60 * 1000).toLocaleDateString(),
|
|
removedBy: 'Demo User',
|
|
size: '15.7 MB',
|
|
path: '/backups/',
|
|
daysInTrash: 12
|
|
},
|
|
{
|
|
id: '3',
|
|
name: 'Draft Presentation.pptx',
|
|
type: 'pptx',
|
|
removedAt: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toLocaleDateString(),
|
|
removedBy: 'Demo User',
|
|
size: '8.2 MB',
|
|
path: '/presentations/',
|
|
daysInTrash: 3
|
|
},
|
|
{
|
|
id: '4',
|
|
name: 'Old Archive.zip',
|
|
type: 'zip',
|
|
removedAt: new Date(Date.now() - 25 * 24 * 60 * 60 * 1000).toLocaleDateString(),
|
|
removedBy: 'Demo User',
|
|
size: '124.5 MB',
|
|
path: '/archives/',
|
|
daysInTrash: 25
|
|
},
|
|
{
|
|
id: '5',
|
|
name: 'Temp Files',
|
|
type: 'folder',
|
|
removedAt: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toLocaleDateString(),
|
|
removedBy: 'Demo User',
|
|
size: '456 KB',
|
|
path: '/temp/',
|
|
daysInTrash: 1
|
|
}
|
|
];
|
|
|
|
const generateMockAIProviders = () => [
|
|
{
|
|
id: 'longcat',
|
|
name: 'LongCat AI',
|
|
description: 'Fast and efficient AI models',
|
|
models: ['LongCat-Flash-Chat', 'LongCat-Flash-Thinking'],
|
|
enabled: true,
|
|
api_key_configured: true
|
|
},
|
|
{
|
|
id: 'mistral',
|
|
name: 'Mistral AI',
|
|
description: 'European AI models',
|
|
models: ['mistral-small-latest', 'mistral-large-latest'],
|
|
enabled: false,
|
|
api_key_configured: false
|
|
}
|
|
];
|
|
|
|
const generateMockFiles = () => [
|
|
{
|
|
id: 1,
|
|
original_name: 'product-roadmap-q2.pdf',
|
|
mime_type: 'application/pdf',
|
|
file_size: 2_517_432,
|
|
description: 'Q2 roadmap and milestones',
|
|
created_at: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(),
|
|
},
|
|
{
|
|
id: 2,
|
|
original_name: 'meeting-notes.md',
|
|
mime_type: 'text/markdown',
|
|
file_size: 15_384,
|
|
description: 'Weekly sync notes',
|
|
created_at: new Date(Date.now() - 4 * 24 * 60 * 60 * 1000).toISOString(),
|
|
},
|
|
{
|
|
id: 3,
|
|
original_name: 'architecture-diagram.png',
|
|
mime_type: 'image/png',
|
|
file_size: 842_112,
|
|
description: 'System architecture overview',
|
|
created_at: new Date(Date.now() - 8 * 24 * 60 * 60 * 1000).toISOString(),
|
|
},
|
|
];
|
|
|
|
const generateMockAISettings = () => ({
|
|
mistral: {
|
|
enabled: false,
|
|
api_key: '',
|
|
model: 'mistral-small-latest',
|
|
model_thinking: 'mistral-large-latest',
|
|
},
|
|
grok: {
|
|
enabled: false,
|
|
api_key: '',
|
|
base_url: 'https://api.x.ai/v1',
|
|
model: 'grok-4-1-fast-non-reasoning-latest',
|
|
model_thinking: 'grok-4-1-fast-reasoning-latest',
|
|
},
|
|
deepseek: {
|
|
enabled: false,
|
|
api_key: '',
|
|
base_url: 'https://api.deepseek.com',
|
|
model: 'deepseek-chat',
|
|
model_thinking: 'deepseek-reasoner',
|
|
},
|
|
ollama: {
|
|
enabled: false,
|
|
base_url: 'http://localhost:11434',
|
|
model: 'llama3.1',
|
|
model_thinking: 'llama3.1',
|
|
},
|
|
longcat: {
|
|
enabled: true,
|
|
api_key: '********',
|
|
base_url: 'https://api.longcat.chat',
|
|
openai_endpoint: 'https://api.longcat.chat/openai',
|
|
anthropic_endpoint: 'https://api.longcat.chat/anthropic',
|
|
model: 'LongCat-Flash-Chat',
|
|
model_thinking: 'LongCat-Flash-Thinking',
|
|
model_thinking_upgraded: 'LongCat-Flash-Thinking-2601',
|
|
format: 'openai',
|
|
},
|
|
openrouter: {
|
|
enabled: false,
|
|
api_key: '',
|
|
base_url: 'https://openrouter.ai/api',
|
|
model: 'openrouter/auto',
|
|
model_thinking: 'openrouter/auto',
|
|
},
|
|
});
|
|
|
|
const generateMockSearchSettings = () => ({
|
|
brave_api_key: '',
|
|
brave_search_base_url: 'https://api.search.brave.com/res/v1/web/search',
|
|
serper_api_key: '',
|
|
serper_base_url: 'https://google.serper.dev/search',
|
|
search_api_provider: 'brave',
|
|
search_results_limit: 10,
|
|
search_cache_ttl: 300,
|
|
search_rate_limit: 100,
|
|
});
|
|
|
|
const generateMockEmailSettings = () => ({
|
|
smtp_enabled: false,
|
|
smtp_host: '',
|
|
smtp_port: 587,
|
|
smtp_username: '',
|
|
smtp_password: '',
|
|
smtp_from_email: '',
|
|
smtp_from_name: 'Trackeep',
|
|
smtp_encryption: 'tls',
|
|
oauth_enabled: false,
|
|
oauth_provider: 'google',
|
|
oauth_client_id: '',
|
|
oauth_client_secret: '',
|
|
oauth_redirect_uri: '',
|
|
});
|
|
|
|
const generateMockMembersDirectory = () =>
|
|
generateMockMembers().map((member) => ({
|
|
id: Number(member.user?.id || member.user_id || 0),
|
|
username: member.user?.username || `user-${member.user_id}`,
|
|
full_name: member.user?.full_name || member.user?.username || 'Member',
|
|
name: member.user?.full_name || member.user?.username || 'Member',
|
|
email: member.user?.email || '',
|
|
role: member.role,
|
|
}));
|
|
|
|
const generateMockChatSessionsExtended = () =>
|
|
generateMockChatSessions().map((session) => ({
|
|
...session,
|
|
include_bookmarks: true,
|
|
include_tasks: true,
|
|
include_files: true,
|
|
include_notes: true,
|
|
last_message_at: session.updated_at,
|
|
}));
|
|
|
|
const generateMockChatMessages = (sessionId: number) => [
|
|
{
|
|
id: sessionId * 100 + 1,
|
|
content: 'Can you summarize my pending tasks for this week?',
|
|
role: 'user',
|
|
created_at: new Date(Date.now() - 50 * 60 * 1000).toISOString(),
|
|
token_count: 22,
|
|
context_items: ['tasks'],
|
|
},
|
|
{
|
|
id: sessionId * 100 + 2,
|
|
content: 'You have 6 open tasks. Two are high priority and due in the next 48 hours.',
|
|
role: 'assistant',
|
|
created_at: new Date(Date.now() - 49 * 60 * 1000).toISOString(),
|
|
token_count: 31,
|
|
context_items: ['tasks', 'notes'],
|
|
},
|
|
];
|
|
|
|
const generateMockAnalyticsDashboard = (days: number) => {
|
|
const safeDays = Number.isFinite(days) && days > 0 ? Math.floor(days) : 30;
|
|
const endDate = new Date();
|
|
const startDate = new Date(endDate);
|
|
startDate.setDate(endDate.getDate() - safeDays + 1);
|
|
|
|
return {
|
|
period: {
|
|
start_date: startDate.toISOString(),
|
|
end_date: endDate.toISOString(),
|
|
days: safeDays,
|
|
},
|
|
summary: {
|
|
hours_tracked: 62.5,
|
|
tasks_completed: 28,
|
|
bookmarks_added: 19,
|
|
notes_created: 11,
|
|
courses_completed: 2,
|
|
github_commits: 34,
|
|
},
|
|
analytics: [
|
|
{
|
|
date: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(),
|
|
hours_tracked: 5.5,
|
|
tasks_completed: 3,
|
|
bookmarks_added: 2,
|
|
notes_created: 1,
|
|
courses_completed: 0,
|
|
github_commits: 4,
|
|
study_streak: 5,
|
|
productivity_score: 78,
|
|
},
|
|
{
|
|
date: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(),
|
|
hours_tracked: 7.25,
|
|
tasks_completed: 4,
|
|
bookmarks_added: 3,
|
|
notes_created: 2,
|
|
courses_completed: 1,
|
|
github_commits: 6,
|
|
study_streak: 6,
|
|
productivity_score: 84,
|
|
},
|
|
{
|
|
date: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(),
|
|
hours_tracked: 6.75,
|
|
tasks_completed: 5,
|
|
bookmarks_added: 1,
|
|
notes_created: 2,
|
|
courses_completed: 0,
|
|
github_commits: 5,
|
|
study_streak: 7,
|
|
productivity_score: 88,
|
|
},
|
|
],
|
|
productivity_metrics: [
|
|
{
|
|
period: 'current',
|
|
start_date: startDate.toISOString(),
|
|
end_date: endDate.toISOString(),
|
|
total_hours: 62.5,
|
|
billable_hours: 48,
|
|
non_billable_hours: 14.5,
|
|
tasks_completed: 28,
|
|
average_task_time: 1.7,
|
|
peak_productivity_hour: 10,
|
|
focus_score: 81,
|
|
efficiency_score: 86,
|
|
},
|
|
],
|
|
learning_analytics: [
|
|
{
|
|
id: 1,
|
|
course: {
|
|
title: 'SolidJS Advanced Patterns',
|
|
description: 'Improve component architecture and performance',
|
|
},
|
|
start_date: new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString(),
|
|
last_accessed: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(),
|
|
time_spent: 14.25,
|
|
progress: 72,
|
|
modules_completed: 9,
|
|
total_modules: 12,
|
|
average_score: 91,
|
|
streak_days: 6,
|
|
skills_acquired: ['SolidJS', 'State Management', 'Performance Tuning'],
|
|
},
|
|
],
|
|
github_analytics: [
|
|
{
|
|
date: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
|
|
commits: 5,
|
|
pull_requests: 1,
|
|
issues_opened: 1,
|
|
issues_closed: 2,
|
|
reviews: 2,
|
|
contributions: 11,
|
|
languages: {
|
|
TypeScript: 65,
|
|
Go: 30,
|
|
CSS: 5,
|
|
},
|
|
repositories: ['trackeep', 'solidjs-components'],
|
|
},
|
|
],
|
|
goals: [
|
|
{
|
|
id: 1,
|
|
title: 'Ship messaging improvements',
|
|
description: 'Complete remaining demo-mode wiring and QA',
|
|
category: 'development',
|
|
target_value: 10,
|
|
current_value: 7,
|
|
unit: 'tasks',
|
|
deadline: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000).toISOString(),
|
|
status: 'in_progress',
|
|
priority: 'high',
|
|
progress: 70,
|
|
is_completed: false,
|
|
milestones: [
|
|
{
|
|
id: 1,
|
|
title: 'Implement API handlers',
|
|
target_value: 1,
|
|
current_value: 1,
|
|
deadline: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
|
|
status: 'completed',
|
|
is_completed: true,
|
|
},
|
|
{
|
|
id: 2,
|
|
title: 'Regression QA pass',
|
|
target_value: 1,
|
|
current_value: 0,
|
|
deadline: new Date(Date.now() + 3 * 24 * 60 * 60 * 1000).toISOString(),
|
|
status: 'pending',
|
|
is_completed: false,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
habit_analytics: [
|
|
{
|
|
habit_name: 'Daily planning',
|
|
start_date: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(),
|
|
last_completed: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
|
|
streak: 7,
|
|
best_streak: 11,
|
|
total_days: 30,
|
|
completion_rate: 83,
|
|
frequency: 'daily',
|
|
category: 'productivity',
|
|
goal_target: 30,
|
|
goal_achieved: false,
|
|
},
|
|
],
|
|
};
|
|
};
|
|
|
|
const generateMockSavedSearches = () => [
|
|
{
|
|
id: 1,
|
|
name: 'Urgent tasks',
|
|
query: 'priority:high status:open',
|
|
filters: { content_type: 'tasks', priority: ['high', 'urgent'] },
|
|
alert: true,
|
|
last_run: new Date(Date.now() - 12 * 60 * 60 * 1000).toISOString(),
|
|
run_count: 12,
|
|
is_public: false,
|
|
description: 'Track high-priority open tasks',
|
|
tags: [
|
|
{ id: 1, name: 'tasks', color: '#f59e0b' },
|
|
{ id: 2, name: 'priority', color: '#ef4444' },
|
|
],
|
|
created_at: new Date(Date.now() - 20 * 24 * 60 * 60 * 1000).toISOString(),
|
|
updated_at: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(),
|
|
},
|
|
{
|
|
id: 2,
|
|
name: 'API docs notes',
|
|
query: 'api documentation',
|
|
filters: { content_type: 'notes', tags: ['docs'] },
|
|
alert: false,
|
|
last_run: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(),
|
|
run_count: 6,
|
|
is_public: true,
|
|
description: 'Notes related to API and docs',
|
|
tags: [{ id: 3, name: 'docs', color: '#3b82f6' }],
|
|
created_at: new Date(Date.now() - 45 * 24 * 60 * 60 * 1000).toISOString(),
|
|
updated_at: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(),
|
|
},
|
|
];
|
|
|
|
const generateMockMessagesConversations = () => {
|
|
const now = Date.now();
|
|
const mkConversation = (id: number, type: 'global' | 'team' | 'group' | 'dm', name: string, topic: string, teamId?: number) => ({
|
|
id,
|
|
type,
|
|
name,
|
|
topic,
|
|
team_id: teamId ?? null,
|
|
created_by: 1,
|
|
is_default: id === 1,
|
|
is_archived: false,
|
|
last_message_at: new Date(now - id * 12 * 60 * 1000).toISOString(),
|
|
created_at: new Date(now - id * 30 * 24 * 60 * 60 * 1000).toISOString(),
|
|
updated_at: new Date(now - id * 10 * 60 * 1000).toISOString(),
|
|
});
|
|
|
|
const mkMessage = (id: number, conversationId: number, senderId: number, body: string) => ({
|
|
id,
|
|
conversation_id: conversationId,
|
|
sender_id: senderId,
|
|
sender: {
|
|
id: senderId,
|
|
username: senderId === 1 ? 'demo' : senderId === 2 ? 'alice' : 'bob',
|
|
full_name: senderId === 1 ? 'Demo User' : senderId === 2 ? 'Alice Johnson' : 'Bob Smith',
|
|
},
|
|
body,
|
|
is_sensitive: false,
|
|
edited_at: null,
|
|
deleted_at: null,
|
|
metadata_json: '{}',
|
|
created_at: new Date(now - id * 5 * 60 * 1000).toISOString(),
|
|
updated_at: new Date(now - id * 5 * 60 * 1000).toISOString(),
|
|
attachments: [],
|
|
references: [],
|
|
suggestions: [],
|
|
reactions: [],
|
|
});
|
|
|
|
const general = mkConversation(1, 'global', 'General', 'Team-wide updates');
|
|
const team = mkConversation(2, 'team', 'Engineering', 'Sprint and release discussion', 1);
|
|
const dm = mkConversation(3, 'dm', 'Direct: Alice', 'One-on-one updates');
|
|
|
|
return [
|
|
{
|
|
conversation: general,
|
|
role: 'owner',
|
|
unread_count: 0,
|
|
last_message: mkMessage(101, general.id, 2, 'Deploy finished successfully.'),
|
|
},
|
|
{
|
|
conversation: team,
|
|
role: 'admin',
|
|
unread_count: 2,
|
|
last_message: mkMessage(102, team.id, 3, 'I pushed a fix for message search.'),
|
|
},
|
|
{
|
|
conversation: dm,
|
|
role: 'member',
|
|
unread_count: 1,
|
|
last_message: mkMessage(103, dm.id, 2, 'Can you review the latest PR?'),
|
|
},
|
|
];
|
|
};
|
|
|
|
const generateMockMessagesForConversation = (conversationId: number) => {
|
|
const now = Date.now();
|
|
const mk = (id: number, senderId: number, minutesAgo: number, body: string) => ({
|
|
id,
|
|
conversation_id: conversationId,
|
|
sender_id: senderId,
|
|
sender: {
|
|
id: senderId,
|
|
username: senderId === 1 ? 'demo' : senderId === 2 ? 'alice' : 'bob',
|
|
full_name: senderId === 1 ? 'Demo User' : senderId === 2 ? 'Alice Johnson' : 'Bob Smith',
|
|
},
|
|
body,
|
|
is_sensitive: false,
|
|
edited_at: null,
|
|
deleted_at: null,
|
|
metadata_json: '{}',
|
|
created_at: new Date(now - minutesAgo * 60 * 1000).toISOString(),
|
|
updated_at: new Date(now - minutesAgo * 60 * 1000).toISOString(),
|
|
attachments: [],
|
|
references: [],
|
|
suggestions: [],
|
|
reactions: [],
|
|
});
|
|
|
|
return [
|
|
mk(conversationId * 1000 + 1, 2, 45, 'Morning standup complete.'),
|
|
mk(conversationId * 1000 + 2, 1, 42, 'Great, I will update the board.'),
|
|
mk(conversationId * 1000 + 3, 3, 20, 'Search indexing patch is merged.'),
|
|
];
|
|
};
|
|
|
|
const generateMockBrowserExtensionApiKeys = () => [
|
|
{
|
|
id: 1,
|
|
name: 'Chrome Extension - Main',
|
|
permissions: ['bookmarks:read', 'bookmarks:write', 'files:read'],
|
|
is_active: true,
|
|
last_used: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(),
|
|
expires_at: new Date(Date.now() + 180 * 24 * 60 * 60 * 1000).toISOString(),
|
|
created_at: new Date(Date.now() - 12 * 24 * 60 * 60 * 1000).toISOString(),
|
|
updated_at: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(),
|
|
},
|
|
];
|
|
|
|
const generateMockBrowserExtensions = () => [
|
|
{
|
|
id: 1,
|
|
extension_id: 'trackeep-chrome-prod',
|
|
name: 'Trackeep Chrome Extension',
|
|
is_active: true,
|
|
last_seen: new Date(Date.now() - 35 * 60 * 1000).toISOString(),
|
|
created_at: new Date(Date.now() - 20 * 24 * 60 * 60 * 1000).toISOString(),
|
|
updated_at: new Date(Date.now() - 35 * 60 * 1000).toISOString(),
|
|
},
|
|
];
|
|
|
|
// Store original fetch at module level
|
|
let originalFetch: typeof fetch | null = null;
|
|
|
|
// Request cache to prevent duplicate API calls
|
|
const requestCache = new Map<string, Promise<Response>>();
|
|
const CACHE_TTL = 2000; // 2 seconds
|
|
|
|
// Generate cache key for requests
|
|
const getCacheKey = (url: string, options?: RequestInit): string => {
|
|
const method = options?.method || 'GET';
|
|
const body = options?.body || '';
|
|
return `${method}:${url}:${body}`;
|
|
};
|
|
|
|
const jsonResponse = (payload: unknown, status: number = 200): Response =>
|
|
new Response(JSON.stringify(payload), {
|
|
status,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
});
|
|
|
|
const parseBody = (options?: RequestInit): Record<string, any> => {
|
|
if (!options?.body || typeof options.body !== 'string') {
|
|
return {};
|
|
}
|
|
try {
|
|
return JSON.parse(options.body);
|
|
} catch {
|
|
return {};
|
|
}
|
|
};
|
|
|
|
// Demo mode fetch interceptor
|
|
export const demoFetch = async (url: string, options?: RequestInit): Promise<Response> => {
|
|
const shouldUseReal = shouldUseRealAPIs();
|
|
|
|
if (shouldUseReal) {
|
|
if (url.includes('youtube') && Math.random() < 0.02) {
|
|
}
|
|
|
|
if (url.includes('youtube')) {
|
|
const cacheKey = getCacheKey(url, options);
|
|
const cachedRequest = requestCache.get(cacheKey);
|
|
if (cachedRequest) {
|
|
return cachedRequest;
|
|
}
|
|
|
|
const requestPromise = (originalFetch || window.fetch)(url, options);
|
|
requestCache.set(cacheKey, requestPromise);
|
|
setTimeout(() => {
|
|
requestCache.delete(cacheKey);
|
|
}, CACHE_TTL);
|
|
return requestPromise;
|
|
}
|
|
|
|
return (originalFetch || window.fetch)(url, options);
|
|
}
|
|
|
|
if (!isDemoMode()) {
|
|
return (originalFetch || window.fetch)(url, options);
|
|
}
|
|
|
|
let path = url;
|
|
let searchParams = new URLSearchParams();
|
|
try {
|
|
const parsed = new URL(url, window.location.origin);
|
|
path = parsed.pathname;
|
|
searchParams = parsed.searchParams;
|
|
} catch {
|
|
// keep defaults
|
|
}
|
|
|
|
const method = (options?.method || 'GET').toUpperCase();
|
|
const body = parseBody(options);
|
|
await new Promise((resolve) => setTimeout(resolve, 180));
|
|
|
|
// Auth endpoints
|
|
if (path.includes('/api/v1/auth/login') || path.includes('/api/v1/auth/login-totp') || path.includes('/api/v1/auth/register')) {
|
|
const user = {
|
|
id: 1,
|
|
email: '[email protected]',
|
|
username: 'demo',
|
|
full_name: 'Demo User',
|
|
theme: 'dark',
|
|
github_id: 123456,
|
|
created_at: new Date(Date.now() - 120 * 24 * 60 * 60 * 1000).toISOString(),
|
|
updated_at: new Date().toISOString(),
|
|
};
|
|
return jsonResponse({
|
|
token: `demo-token-${Date.now()}`,
|
|
user,
|
|
});
|
|
}
|
|
|
|
if (path.includes('/api/v1/auth/check-users')) {
|
|
return jsonResponse({ hasUsers: true });
|
|
}
|
|
|
|
if (path.includes('/api/v1/auth/logout')) {
|
|
return jsonResponse({ message: 'Logged out (demo mode)' });
|
|
}
|
|
|
|
if (path.includes('/api/v1/auth/me')) {
|
|
return jsonResponse({
|
|
user: {
|
|
id: 1,
|
|
email: '[email protected]',
|
|
username: 'demo',
|
|
full_name: 'Demo User',
|
|
theme: 'dark',
|
|
github_id: 123456,
|
|
},
|
|
});
|
|
}
|
|
|
|
if (path.includes('/api/v1/auth/profile') && method === 'PUT') {
|
|
return jsonResponse({
|
|
user: {
|
|
id: 1,
|
|
email: '[email protected]',
|
|
username: 'demo',
|
|
full_name: typeof body.fullName === 'string' && body.fullName.trim() ? body.fullName.trim() : 'Demo User',
|
|
theme: body.theme === 'light' ? 'light' : 'dark',
|
|
updated_at: new Date().toISOString(),
|
|
},
|
|
});
|
|
}
|
|
|
|
if (path.includes('/api/v1/auth/password') && method === 'PUT') {
|
|
return jsonResponse({ message: 'Password changed successfully (demo mode)' });
|
|
}
|
|
|
|
if (path.includes('/api/v1/auth/password-reset/confirm') && method === 'POST') {
|
|
return jsonResponse({ message: 'Password reset successful (demo mode)' });
|
|
}
|
|
|
|
if (path.includes('/api/v1/auth/password-reset') && method === 'POST') {
|
|
return jsonResponse({ message: 'Password reset code sent (demo mode)' });
|
|
}
|
|
|
|
if (path.includes('/api/v1/auth/ai/settings')) {
|
|
if (method === 'GET') {
|
|
return jsonResponse(generateMockAISettings());
|
|
}
|
|
if (method === 'PUT') {
|
|
return jsonResponse(body);
|
|
}
|
|
}
|
|
|
|
if (path.includes('/api/v1/auth/search/settings')) {
|
|
if (method === 'GET') {
|
|
return jsonResponse(generateMockSearchSettings());
|
|
}
|
|
if (method === 'PUT') {
|
|
return jsonResponse(body);
|
|
}
|
|
}
|
|
|
|
if (path.includes('/api/v1/auth/email/settings')) {
|
|
if (method === 'GET') {
|
|
return jsonResponse(generateMockEmailSettings());
|
|
}
|
|
if (method === 'PUT') {
|
|
return jsonResponse({ ...generateMockEmailSettings(), ...body });
|
|
}
|
|
}
|
|
|
|
if (path.includes('/api/v1/auth/email/test') && method === 'POST') {
|
|
return jsonResponse({ message: 'Test email sent successfully (demo mode)' });
|
|
}
|
|
|
|
// Dashboard and analytics
|
|
if (path.includes('/api/v1/dashboard/stats')) {
|
|
return jsonResponse(generateMockStats());
|
|
}
|
|
|
|
if (path.includes('/api/v1/analytics/dashboard')) {
|
|
const days = Number(searchParams.get('days') || '30');
|
|
return jsonResponse(generateMockAnalyticsDashboard(days));
|
|
}
|
|
|
|
// AI endpoints
|
|
if (path.includes('/api/v1/ai/providers') || path.includes('/api/ai/providers')) {
|
|
return jsonResponse({ providers: generateMockAIProviders() });
|
|
}
|
|
|
|
if (path.includes('/api/v1/ai/chat') && method === 'POST') {
|
|
const prompt = typeof body.message === 'string' ? body.message : '';
|
|
return jsonResponse({
|
|
response: `Demo AI response for: "${prompt || 'your message'}"`,
|
|
content: `Demo AI response for: "${prompt || 'your message'}"`,
|
|
provider: body.provider || 'longcat',
|
|
model: body.model || 'LongCat-Flash-Chat',
|
|
});
|
|
}
|
|
|
|
// Files endpoints
|
|
if (path.includes('/api/v1/files/upload') && method === 'POST') {
|
|
return jsonResponse({
|
|
id: Date.now(),
|
|
original_name: 'uploaded-file.txt',
|
|
mime_type: 'text/plain',
|
|
file_size: 2048,
|
|
created_at: new Date().toISOString(),
|
|
description: 'Uploaded from demo mode',
|
|
}, 201);
|
|
}
|
|
|
|
if (/^\/api\/v1\/files\/\d+\/download$/.test(path)) {
|
|
return jsonResponse({ url: '#', message: 'Demo download link generated' });
|
|
}
|
|
|
|
if (path.includes('/api/v1/files') && method === 'GET') {
|
|
return jsonResponse(generateMockFiles());
|
|
}
|
|
|
|
// Team/member endpoints (specific before generic /teams)
|
|
if (/^\/api\/v1\/teams\/\d+\/members\/\d+$/.test(path) && method === 'DELETE') {
|
|
return jsonResponse({ message: 'Member removed successfully (demo mode)' });
|
|
}
|
|
|
|
if (/^\/api\/v1\/teams\/\d+\/invite$/.test(path) && method === 'POST') {
|
|
return jsonResponse({ message: 'Invitation sent successfully (demo mode)' });
|
|
}
|
|
|
|
if (/^\/api\/v1\/teams\/\d+\/members$/.test(path) && method === 'GET') {
|
|
return jsonResponse({ members: generateMockMembers() });
|
|
}
|
|
|
|
if (path.includes('/api/v1/members') && method === 'GET') {
|
|
return jsonResponse({ members: generateMockMembersDirectory() });
|
|
}
|
|
|
|
if (path.includes('/api/v1/teams') && method === 'GET') {
|
|
return jsonResponse({ teams: generateMockTeams() });
|
|
}
|
|
|
|
// GitHub endpoints
|
|
if (path.includes('/api/v1/github/repos') && method === 'GET') {
|
|
return jsonResponse({ repos: generateMockGitHubRepos() });
|
|
}
|
|
|
|
// Time entries
|
|
if (path.includes('/api/v1/time-entries') && method === 'GET') {
|
|
return jsonResponse({ time_entries: generateMockTimeEntries() });
|
|
}
|
|
|
|
if (path.includes('/api/v1/time-entries') && method === 'POST') {
|
|
return jsonResponse({ ...body, id: Date.now() }, 201);
|
|
}
|
|
|
|
// Bookmarks/tasks/notes GET
|
|
if (path.includes('/api/v1/bookmarks') && method === 'GET') {
|
|
const { getMockBookmarks } = await import('./mockData');
|
|
const mockBookmarks = getMockBookmarks().map((bookmark, index) => ({
|
|
id: index + 1,
|
|
title: bookmark.title,
|
|
url: bookmark.url,
|
|
description: bookmark.description,
|
|
tags: bookmark.tags,
|
|
created_at: bookmark.createdAt,
|
|
is_favorite: bookmark.tags.some((tag) => tag.name === 'important' || tag.name === 'favorite'),
|
|
favicon: bookmark.favicon,
|
|
screenshot: bookmark.screenshot,
|
|
screenshot_medium: bookmark.screenshot,
|
|
}));
|
|
return jsonResponse(mockBookmarks);
|
|
}
|
|
|
|
if (path.includes('/api/v1/tasks') && method === 'GET') {
|
|
const { getMockTasks } = await import('./mockData');
|
|
const mockTasks = getMockTasks().map((task, index) => {
|
|
const status = task.status === 'completed' ? 'completed' : 'pending';
|
|
return {
|
|
id: index + 1,
|
|
title: task.title,
|
|
description: task.description,
|
|
completed: status === 'completed',
|
|
status,
|
|
priority: task.priority,
|
|
createdAt: task.createdAt,
|
|
created_at: task.createdAt,
|
|
dueDate: task.dueDate,
|
|
due_date: task.dueDate,
|
|
};
|
|
});
|
|
return jsonResponse(mockTasks);
|
|
}
|
|
|
|
if (path.includes('/api/v1/notes') && method === 'GET') {
|
|
const { getMockNotes } = await import('./mockData');
|
|
const mockNotes = getMockNotes().map((note, index) => ({
|
|
id: index + 1,
|
|
title: note.title,
|
|
content: note.content,
|
|
tags: note.tags,
|
|
created_at: note.createdAt,
|
|
updated_at: note.updatedAt,
|
|
pinned: note.tags.some((tag) => tag.name === 'pinned'),
|
|
is_pinned: note.tags.some((tag) => tag.name === 'pinned'),
|
|
attachments: note.attachments || [],
|
|
}));
|
|
return jsonResponse(mockNotes);
|
|
}
|
|
|
|
// Bookmark/task/note mutations
|
|
if (path.includes('/api/v1/bookmarks') && method === 'POST') {
|
|
return jsonResponse({
|
|
id: Date.now(),
|
|
title: body.title || 'Untitled bookmark',
|
|
url: body.url || '',
|
|
description: body.description || '',
|
|
tags: body.tags || [],
|
|
created_at: new Date().toISOString(),
|
|
is_favorite: Boolean(body.is_favorite),
|
|
favicon: body.favicon || '',
|
|
screenshot: body.screenshot || '',
|
|
screenshot_medium: body.screenshot_medium || '',
|
|
}, 201);
|
|
}
|
|
|
|
if (path.includes('/api/v1/tasks') && method === 'POST') {
|
|
const completed = Boolean(body.completed);
|
|
return jsonResponse({
|
|
id: Date.now(),
|
|
title: body.title || 'Untitled task',
|
|
description: body.description || '',
|
|
completed,
|
|
status: completed ? 'completed' : 'pending',
|
|
priority: body.priority || 'medium',
|
|
createdAt: new Date().toISOString(),
|
|
created_at: new Date().toISOString(),
|
|
dueDate: body.dueDate || '',
|
|
due_date: body.dueDate || '',
|
|
}, 201);
|
|
}
|
|
|
|
if (path.includes('/api/v1/notes') && method === 'POST') {
|
|
return jsonResponse({
|
|
id: Date.now(),
|
|
title: body.title || 'Untitled note',
|
|
content: body.content || '',
|
|
tags: body.tags || [],
|
|
created_at: new Date().toISOString(),
|
|
updated_at: new Date().toISOString(),
|
|
pinned: Boolean(body.pinned),
|
|
is_pinned: Boolean(body.pinned),
|
|
attachments: body.attachments || [],
|
|
}, 201);
|
|
}
|
|
|
|
if (path.includes('/api/v1/bookmarks') && method === 'PUT') {
|
|
const pathParts = path.split('/');
|
|
const idFromPath = Number(pathParts[pathParts.length - 1] || 0);
|
|
return jsonResponse({
|
|
id: idFromPath || body.id || Date.now(),
|
|
title: body.title || 'Untitled bookmark',
|
|
url: body.url || '',
|
|
description: body.description || '',
|
|
tags: body.tags || [],
|
|
created_at: body.created_at || new Date().toISOString(),
|
|
is_favorite: Boolean(body.is_favorite),
|
|
favicon: body.favicon || '',
|
|
screenshot: body.screenshot || '',
|
|
screenshot_medium: body.screenshot_medium || '',
|
|
});
|
|
}
|
|
|
|
if (path.includes('/api/v1/tasks') && method === 'PUT') {
|
|
const pathParts = path.split('/');
|
|
const idFromPath = Number(pathParts[pathParts.length - 1] || 0);
|
|
const completed = Boolean(body.completed);
|
|
return jsonResponse({
|
|
id: idFromPath || body.id || Date.now(),
|
|
title: body.title || 'Untitled task',
|
|
description: body.description || '',
|
|
completed,
|
|
status: completed ? 'completed' : 'pending',
|
|
priority: body.priority || 'medium',
|
|
createdAt: body.createdAt || new Date().toISOString(),
|
|
dueDate: body.dueDate || '',
|
|
due_date: body.dueDate || '',
|
|
});
|
|
}
|
|
|
|
if (path.includes('/api/v1/notes/') && path.endsWith('/pin') && method === 'PUT') {
|
|
const parts = path.split('/');
|
|
const noteId = Number(parts[parts.length - 2] || 0);
|
|
return jsonResponse({
|
|
id: noteId,
|
|
pinned: Boolean(body.pinned),
|
|
is_pinned: Boolean(body.pinned),
|
|
updated_at: new Date().toISOString(),
|
|
});
|
|
}
|
|
|
|
if (path.includes('/api/v1/notes') && method === 'PUT') {
|
|
const pathParts = path.split('/');
|
|
const idFromPath = Number(pathParts[pathParts.length - 1] || 0);
|
|
return jsonResponse({
|
|
id: idFromPath || body.id || Date.now(),
|
|
title: body.title || 'Untitled note',
|
|
content: body.content || '',
|
|
tags: body.tags || [],
|
|
created_at: body.created_at || new Date().toISOString(),
|
|
updated_at: new Date().toISOString(),
|
|
pinned: Boolean(body.pinned),
|
|
is_pinned: Boolean(body.pinned),
|
|
attachments: body.attachments || [],
|
|
});
|
|
}
|
|
|
|
if (path.includes('/api/v1/bookmarks') && method === 'DELETE') {
|
|
return jsonResponse({ message: 'Bookmark deleted (demo mode)' });
|
|
}
|
|
if (path.includes('/api/v1/tasks') && method === 'DELETE') {
|
|
return jsonResponse({ message: 'Task deleted (demo mode)' });
|
|
}
|
|
if (path.includes('/api/v1/notes') && method === 'DELETE') {
|
|
return jsonResponse({ message: 'Note deleted (demo mode)' });
|
|
}
|
|
|
|
// YouTube/video endpoints
|
|
if (path.includes('/api/v1/video-bookmarks')) {
|
|
if (method === 'GET') {
|
|
return jsonResponse({ bookmarks: generateMockVideoBookmarks() });
|
|
}
|
|
if (method === 'POST') {
|
|
return jsonResponse({ id: Date.now(), message: 'Bookmark created successfully (demo mode)' }, 201);
|
|
}
|
|
}
|
|
|
|
if (path.includes('/api/v1/youtube/videos') && method === 'GET') {
|
|
return jsonResponse(generateMockVideoBookmarks());
|
|
}
|
|
|
|
if (path.includes('/api/v1/youtube/search')) {
|
|
const { getMockVideos } = await import('./mockData');
|
|
const query = String(body.query || '').toLowerCase();
|
|
const mockVideos = getMockVideos()
|
|
.filter((video) =>
|
|
video.title.toLowerCase().includes(query) ||
|
|
video.description.toLowerCase().includes(query) ||
|
|
video.channel.toLowerCase().includes(query)
|
|
)
|
|
.slice(0, 10)
|
|
.map((video) => ({
|
|
id: video.id,
|
|
title: video.title,
|
|
channel_title: video.channel,
|
|
duration: video.duration,
|
|
published_at: video.publishedAt,
|
|
view_count: Math.floor(Math.random() * 100000) + 1000,
|
|
}));
|
|
return jsonResponse({ videos: mockVideos });
|
|
}
|
|
|
|
if (path.includes('/api/v1/youtube/channel-videos')) {
|
|
const { getMockVideos } = await import('./mockData');
|
|
const mockVideos = getMockVideos().slice(0, 5).map((video) => ({
|
|
video_id: video.id,
|
|
title: video.title,
|
|
channel: video.channel,
|
|
length: video.duration,
|
|
views: Math.floor(Math.random() * 100000) + 1000,
|
|
published_date: video.publishedAt,
|
|
published_text: new Date(video.publishedAt).toLocaleDateString(),
|
|
}));
|
|
return jsonResponse({ videos: mockVideos });
|
|
}
|
|
|
|
if (path.includes('/api/v1/youtube/video-details')) {
|
|
return jsonResponse(generateMockVideoBookmarks()[0]);
|
|
}
|
|
|
|
if (path.includes('/api/v1/youtube/predefined-channels')) {
|
|
return jsonResponse(generateMockVideoBookmarks());
|
|
}
|
|
|
|
// Learning paths
|
|
if (path.includes('/api/v1/learning-paths/categories')) {
|
|
return jsonResponse(['frontend', 'backend', 'fullstack', 'devops', 'mobile', 'design']);
|
|
}
|
|
|
|
if (path.includes('/api/v1/learning-paths')) {
|
|
return jsonResponse(generateMockLearningPaths());
|
|
}
|
|
|
|
// Chat endpoints
|
|
const chatMessagesMatch = path.match(/^\/api\/v1\/chat\/sessions\/(\d+)\/messages$/);
|
|
if (chatMessagesMatch && method === 'GET') {
|
|
const sessionId = Number(chatMessagesMatch[1]);
|
|
return jsonResponse(generateMockChatMessages(sessionId));
|
|
}
|
|
|
|
const chatSessionDeleteMatch = path.match(/^\/api\/v1\/chat\/sessions\/(\d+)$/);
|
|
if (chatSessionDeleteMatch && method === 'DELETE') {
|
|
return jsonResponse({ message: 'Session deleted (demo mode)' });
|
|
}
|
|
|
|
if (path === '/api/v1/chat/sessions' && method === 'GET') {
|
|
return jsonResponse(generateMockChatSessionsExtended());
|
|
}
|
|
|
|
if (path === '/api/v1/chat/send' && method === 'POST') {
|
|
const sessionId = Number(body.session_id || generateMockChatSessionsExtended()[0].id);
|
|
return jsonResponse({
|
|
session_id: sessionId,
|
|
message: `Demo assistant reply to: ${body.message || 'your prompt'}`,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
|
|
// Messages module endpoints
|
|
if (path.startsWith('/api/v1/messages')) {
|
|
const conversations = generateMockMessagesConversations();
|
|
const defaultConversation = conversations[0];
|
|
|
|
if (path === '/api/v1/messages/conversations' && method === 'GET') {
|
|
return jsonResponse({ conversations });
|
|
}
|
|
|
|
if (path === '/api/v1/messages/conversations' && method === 'POST') {
|
|
const type = typeof body.type === 'string' ? body.type : 'group';
|
|
const conversation = {
|
|
id: Date.now(),
|
|
type,
|
|
name: typeof body.name === 'string' && body.name.trim() ? body.name.trim() : 'New conversation',
|
|
topic: typeof body.topic === 'string' ? body.topic : '',
|
|
team_id: Number(body.team_id || 0) || null,
|
|
created_by: 1,
|
|
is_default: false,
|
|
is_archived: false,
|
|
last_message_at: new Date().toISOString(),
|
|
created_at: new Date().toISOString(),
|
|
updated_at: new Date().toISOString(),
|
|
};
|
|
return jsonResponse({ conversation }, 201);
|
|
}
|
|
|
|
const conversationDetailMatch = path.match(/^\/api\/v1\/messages\/conversations\/(\d+)$/);
|
|
if (conversationDetailMatch && method === 'GET') {
|
|
const conversationId = Number(conversationDetailMatch[1]);
|
|
const found = conversations.find((entry) => entry.conversation.id === conversationId)?.conversation || defaultConversation.conversation;
|
|
const members = generateMockMembers().map((member, index) => ({
|
|
id: index + 1,
|
|
conversation_id: conversationId,
|
|
user_id: Number(member.user?.id || member.user_id || index + 1),
|
|
role: member.role,
|
|
joined_at: member.joined_at,
|
|
last_read_message_id: null,
|
|
last_read_at: null,
|
|
muted_until: null,
|
|
is_hidden: false,
|
|
user: {
|
|
id: Number(member.user?.id || member.user_id || index + 1),
|
|
username: member.user?.username || `member-${index + 1}`,
|
|
full_name: member.user?.full_name || member.user?.username || 'Member',
|
|
avatar_url: '',
|
|
},
|
|
}));
|
|
return jsonResponse({
|
|
conversation: found,
|
|
membership: members.find((member) => member.user_id === 1) || members[0],
|
|
members,
|
|
});
|
|
}
|
|
|
|
const conversationMessagesMatch = path.match(/^\/api\/v1\/messages\/conversations\/(\d+)\/messages$/);
|
|
if (conversationMessagesMatch && method === 'GET') {
|
|
const conversationId = Number(conversationMessagesMatch[1]);
|
|
return jsonResponse({
|
|
messages: generateMockMessagesForConversation(conversationId),
|
|
next_cursor: null,
|
|
});
|
|
}
|
|
|
|
if (conversationMessagesMatch && method === 'POST') {
|
|
const conversationId = Number(conversationMessagesMatch[1]);
|
|
const newMessage = {
|
|
id: Date.now(),
|
|
conversation_id: conversationId,
|
|
sender_id: 1,
|
|
sender: {
|
|
id: 1,
|
|
username: 'demo',
|
|
full_name: 'Demo User',
|
|
avatar_url: '',
|
|
},
|
|
body: typeof body.body === 'string' ? body.body : '',
|
|
is_sensitive: false,
|
|
edited_at: null,
|
|
deleted_at: null,
|
|
metadata_json: JSON.stringify(body.metadata || {}),
|
|
created_at: new Date().toISOString(),
|
|
updated_at: new Date().toISOString(),
|
|
attachments: Array.isArray(body.attachments) ? body.attachments : [],
|
|
references: Array.isArray(body.references) ? body.references : [],
|
|
suggestions: [],
|
|
reactions: [],
|
|
};
|
|
return jsonResponse({ message: newMessage }, 201);
|
|
}
|
|
|
|
const messageMutationMatch = path.match(/^\/api\/v1\/messages\/messages\/(\d+)$/);
|
|
if (messageMutationMatch && method === 'PATCH') {
|
|
const messageId = Number(messageMutationMatch[1]);
|
|
return jsonResponse({
|
|
message: {
|
|
id: messageId,
|
|
conversation_id: 1,
|
|
sender_id: 1,
|
|
body: typeof body.body === 'string' ? body.body : '',
|
|
is_sensitive: false,
|
|
edited_at: new Date().toISOString(),
|
|
deleted_at: null,
|
|
created_at: new Date(Date.now() - 10 * 60 * 1000).toISOString(),
|
|
updated_at: new Date().toISOString(),
|
|
},
|
|
});
|
|
}
|
|
|
|
if (messageMutationMatch && method === 'DELETE') {
|
|
return jsonResponse({ message: 'Message deleted (demo mode)' });
|
|
}
|
|
|
|
const reactionAddMatch = path.match(/^\/api\/v1\/messages\/messages\/(\d+)\/reactions$/);
|
|
if (reactionAddMatch && method === 'POST') {
|
|
return jsonResponse({
|
|
reaction: {
|
|
id: Date.now(),
|
|
message_id: Number(reactionAddMatch[1]),
|
|
user_id: 1,
|
|
emoji: body.emoji || ':+1:',
|
|
},
|
|
}, 201);
|
|
}
|
|
|
|
const reactionDeleteMatch = path.match(/^\/api\/v1\/messages\/messages\/(\d+)\/reactions\/(.+)$/);
|
|
if (reactionDeleteMatch && method === 'DELETE') {
|
|
return jsonResponse({ message: 'Reaction removed (demo mode)' });
|
|
}
|
|
|
|
if (path === '/api/v1/messages/messages/search' && method === 'POST') {
|
|
const query = typeof body.query === 'string' ? body.query : '';
|
|
const results = generateMockMessagesForConversation(1).filter((message) =>
|
|
query ? message.body.toLowerCase().includes(query.toLowerCase()) : true
|
|
);
|
|
return jsonResponse({
|
|
results,
|
|
total: results.length,
|
|
limit: Number(body.limit || 50),
|
|
offset: Number(body.offset || 0),
|
|
});
|
|
}
|
|
|
|
const suggestionsMatch = path.match(/^\/api\/v1\/messages\/messages\/(\d+)\/suggestions$/);
|
|
if (suggestionsMatch && method === 'GET') {
|
|
const messageId = Number(suggestionsMatch[1]);
|
|
return jsonResponse({
|
|
suggestions: [
|
|
{
|
|
id: 1,
|
|
message_id: messageId,
|
|
type: 'summarize',
|
|
payload_json: JSON.stringify({ summary: 'Condense this message to key bullets.' }),
|
|
status: 'pending',
|
|
created_at: new Date().toISOString(),
|
|
updated_at: new Date().toISOString(),
|
|
},
|
|
],
|
|
});
|
|
}
|
|
|
|
const suggestionActionMatch = path.match(/^\/api\/v1\/messages\/messages\/(\d+)\/suggestions\/(\d+)\/(accept|dismiss)$/);
|
|
if (suggestionActionMatch && method === 'POST') {
|
|
return jsonResponse({ message: 'Suggestion updated (demo mode)' });
|
|
}
|
|
|
|
const revealSensitiveMatch = path.match(/^\/api\/v1\/messages\/messages\/(\d+)\/reveal-sensitive$/);
|
|
if (revealSensitiveMatch && method === 'POST') {
|
|
const messageId = Number(revealSensitiveMatch[1]);
|
|
return jsonResponse({
|
|
message_id: messageId,
|
|
plaintext: 'demo-sensitive-content',
|
|
});
|
|
}
|
|
|
|
if (path === '/api/v1/messages/password-vault/items' && method === 'GET') {
|
|
return jsonResponse({
|
|
items: [
|
|
{
|
|
id: 1,
|
|
label: 'Demo Database Password',
|
|
owner_user_id: 1,
|
|
source_message_id: 1001,
|
|
last_accessed_at: new Date(Date.now() - 3 * 60 * 60 * 1000).toISOString(),
|
|
shared: true,
|
|
allow_reveal: true,
|
|
expires_at: null,
|
|
target_conversation_id: 1,
|
|
},
|
|
],
|
|
});
|
|
}
|
|
|
|
if (path === '/api/v1/messages/password-vault/items' && method === 'POST') {
|
|
return jsonResponse({
|
|
item: {
|
|
id: Date.now(),
|
|
...body,
|
|
owner_user_id: 1,
|
|
shared: false,
|
|
allow_reveal: true,
|
|
},
|
|
}, 201);
|
|
}
|
|
|
|
const vaultRevealMatch = path.match(/^\/api\/v1\/messages\/password-vault\/items\/(\d+)\/reveal$/);
|
|
if (vaultRevealMatch && method === 'POST') {
|
|
return jsonResponse({
|
|
id: Number(vaultRevealMatch[1]),
|
|
label: 'Demo Secret',
|
|
secret: 'demo-secret-value',
|
|
notes: 'Demo reveal payload',
|
|
});
|
|
}
|
|
|
|
const vaultShareMatch = path.match(/^\/api\/v1\/messages\/password-vault\/items\/(\d+)\/share$/);
|
|
if (vaultShareMatch && method === 'POST') {
|
|
return jsonResponse({ message: 'Vault item shared (demo mode)' });
|
|
}
|
|
|
|
const vaultUnshareMatch = path.match(/^\/api\/v1\/messages\/password-vault\/items\/(\d+)\/unshare$/);
|
|
if (vaultUnshareMatch && method === 'POST') {
|
|
return jsonResponse({ message: 'Vault item unshared (demo mode)' });
|
|
}
|
|
}
|
|
|
|
// Browser extension endpoints
|
|
if (path === '/api/v1/browser-extension/validate' && method === 'GET') {
|
|
return jsonResponse({
|
|
valid: true,
|
|
permissions: ['bookmarks:read', 'bookmarks:write', 'files:read'],
|
|
user: { id: 1, username: 'demo' },
|
|
});
|
|
}
|
|
|
|
if (path === '/api/v1/browser-extension/api-keys' && method === 'GET') {
|
|
return jsonResponse(generateMockBrowserExtensionApiKeys());
|
|
}
|
|
|
|
if (path === '/api/v1/browser-extension/extensions' && method === 'GET') {
|
|
return jsonResponse(generateMockBrowserExtensions());
|
|
}
|
|
|
|
if (path === '/api/v1/browser-extension/api-keys/generate' && method === 'POST') {
|
|
return jsonResponse({
|
|
id: Date.now(),
|
|
name: body.name || 'New API Key',
|
|
permissions: Array.isArray(body.permissions) ? body.permissions : [],
|
|
is_active: true,
|
|
key: `tk_demo_${Date.now()}`,
|
|
created_at: new Date().toISOString(),
|
|
updated_at: new Date().toISOString(),
|
|
}, 201);
|
|
}
|
|
|
|
if (/^\/api\/v1\/browser-extension\/api-keys\/\d+$/.test(path) && method === 'DELETE') {
|
|
return jsonResponse({ message: 'API key revoked successfully (demo mode)' });
|
|
}
|
|
|
|
if (/^\/api\/v1\/browser-extension\/extensions\/[^/]+$/.test(path) && method === 'DELETE') {
|
|
return jsonResponse({ message: 'Extension revoked successfully (demo mode)' });
|
|
}
|
|
|
|
// Search endpoints
|
|
if (path.includes('/api/v1/search/semantic') && method === 'POST') {
|
|
const query = typeof body.query === 'string' ? body.query : '';
|
|
const results = [
|
|
{
|
|
id: 1,
|
|
type: 'note',
|
|
title: `Semantic result for "${query || 'query'}"`,
|
|
description: 'Demo semantic search match from notes',
|
|
content: 'This is a semantic match generated for demo mode.',
|
|
tags: [{ id: 1, name: 'semantic', color: '#3b82f6' }],
|
|
created_at: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(),
|
|
updated_at: new Date().toISOString(),
|
|
score: 0.92,
|
|
similarity: 0.92,
|
|
},
|
|
{
|
|
id: 2,
|
|
type: 'bookmark',
|
|
title: `Related bookmark: ${query || 'topic'}`,
|
|
description: 'Demo semantic match from bookmarks',
|
|
content: 'Bookmark content excerpt for semantic demo.',
|
|
tags: [{ id: 2, name: 'bookmark', color: '#10b981' }],
|
|
created_at: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(),
|
|
updated_at: new Date().toISOString(),
|
|
url: 'https://example.com',
|
|
score: 0.84,
|
|
similarity: 0.84,
|
|
},
|
|
];
|
|
return jsonResponse({
|
|
results,
|
|
took: 42,
|
|
});
|
|
}
|
|
|
|
if (path.includes('/api/v1/search/enhanced') && method === 'POST') {
|
|
const query = typeof body.query === 'string' ? body.query : '';
|
|
const filters = typeof body === 'object' && body ? body : {};
|
|
const results = [
|
|
{
|
|
id: 101,
|
|
type: 'task',
|
|
title: `Task match for "${query || 'query'}"`,
|
|
description: 'Demo enhanced search task result',
|
|
content: 'Follow up on API integration tests.',
|
|
tags: [{ id: 11, name: 'task', color: '#f59e0b' }],
|
|
created_at: new Date(Date.now() - 48 * 60 * 60 * 1000).toISOString(),
|
|
updated_at: new Date().toISOString(),
|
|
status: 'pending',
|
|
priority: 'high',
|
|
score: 0.88,
|
|
},
|
|
{
|
|
id: 102,
|
|
type: 'note',
|
|
title: `Note match for "${query || 'query'}"`,
|
|
description: 'Demo enhanced search note result',
|
|
content: 'Implementation notes for demo-mode endpoint wiring.',
|
|
tags: [{ id: 12, name: 'notes', color: '#8b5cf6' }],
|
|
created_at: new Date(Date.now() - 72 * 60 * 60 * 1000).toISOString(),
|
|
updated_at: new Date().toISOString(),
|
|
score: 0.81,
|
|
},
|
|
];
|
|
return jsonResponse({
|
|
results,
|
|
total: results.length,
|
|
query,
|
|
filters,
|
|
took: 37,
|
|
suggestions: query ? [`${query} settings`, `${query} notes`] : [],
|
|
aggregations: {
|
|
tasks: 1,
|
|
notes: 1,
|
|
},
|
|
});
|
|
}
|
|
|
|
if (path === '/api/v1/search/saved/tags' && method === 'GET') {
|
|
return jsonResponse({
|
|
tags: [
|
|
{ id: 1, name: 'tasks', color: '#f59e0b' },
|
|
{ id: 2, name: 'docs', color: '#3b82f6' },
|
|
],
|
|
});
|
|
}
|
|
|
|
if (/^\/api\/v1\/search\/saved\/\d+\/run$/.test(path) && method === 'POST') {
|
|
return jsonResponse({
|
|
results: [
|
|
{
|
|
id: 1,
|
|
type: 'task',
|
|
title: 'Demo saved search result',
|
|
description: 'Result returned by demo saved-search run endpoint',
|
|
content: 'Saved search execution result',
|
|
tags: [{ id: 1, name: 'saved', color: '#10b981' }],
|
|
created_at: new Date().toISOString(),
|
|
updated_at: new Date().toISOString(),
|
|
score: 0.8,
|
|
},
|
|
],
|
|
total: 1,
|
|
took: 28,
|
|
});
|
|
}
|
|
|
|
if (path === '/api/v1/search/saved' && method === 'GET') {
|
|
return jsonResponse({ saved_searches: generateMockSavedSearches() });
|
|
}
|
|
|
|
if (path === '/api/v1/search/saved' && method === 'POST') {
|
|
return jsonResponse({
|
|
saved_search: {
|
|
id: Date.now(),
|
|
...body,
|
|
run_count: 0,
|
|
created_at: new Date().toISOString(),
|
|
updated_at: new Date().toISOString(),
|
|
},
|
|
}, 201);
|
|
}
|
|
|
|
if (/^\/api\/v1\/search\/saved\/\d+$/.test(path) && method === 'PUT') {
|
|
return jsonResponse({
|
|
saved_search: {
|
|
...body,
|
|
updated_at: new Date().toISOString(),
|
|
},
|
|
});
|
|
}
|
|
|
|
if (/^\/api\/v1\/search\/saved\/\d+$/.test(path) && method === 'DELETE') {
|
|
return jsonResponse({ message: 'Saved search deleted (demo mode)' });
|
|
}
|
|
|
|
// Search web/news endpoints
|
|
if (path.includes('/api/v1/search/web') || path.includes('/res/v1/web/search')) {
|
|
const queryParam = (searchParams.get('q') || body.query || '').trim();
|
|
if (!queryParam) {
|
|
return jsonResponse({ error: 'Query parameter required' }, 400);
|
|
}
|
|
const mockSearchResults = [
|
|
{
|
|
title: `${queryParam} - Demo Search Result 1`,
|
|
url: `https://demo.example.com/${queryParam.toLowerCase().replace(/\s+/g, '-')}`,
|
|
description: `This is a demo search result for "${queryParam}" showing how the search functionality works in demo mode.`,
|
|
published_date: new Date().toISOString().split('T')[0],
|
|
language: 'English',
|
|
family_friendly: true,
|
|
type: 'web',
|
|
subtype: 'search',
|
|
},
|
|
{
|
|
title: `${queryParam} - Demo Search Result 2`,
|
|
url: `https://demo-search.com/${queryParam.toLowerCase().replace(/\s+/g, '-')}`,
|
|
description: `Another demo search result for "${queryParam}" demonstrating the search interface in demo mode.`,
|
|
published_date: new Date(Date.now() - 86400000).toISOString().split('T')[0],
|
|
language: 'English',
|
|
family_friendly: true,
|
|
type: 'web',
|
|
subtype: 'search',
|
|
},
|
|
{
|
|
title: `Learn more about ${queryParam}`,
|
|
url: `https://demo-learning.com/${queryParam.toLowerCase().replace(/\s+/g, '-')}`,
|
|
description: `Educational content about ${queryParam} for demo purposes. This shows how search results can include learning resources.`,
|
|
published_date: new Date(Date.now() - 172800000).toISOString().split('T')[0],
|
|
language: 'English',
|
|
family_friendly: true,
|
|
type: 'web',
|
|
subtype: 'educational',
|
|
},
|
|
];
|
|
return jsonResponse({
|
|
results: mockSearchResults,
|
|
web: { results: mockSearchResults },
|
|
query: { original: queryParam, display: queryParam },
|
|
mixed: { results: mockSearchResults },
|
|
});
|
|
}
|
|
|
|
if (path.includes('/api/v1/search/news') || path.includes('/res/v1/news/search')) {
|
|
const queryParam = (searchParams.get('q') || body.query || '').trim();
|
|
if (!queryParam) {
|
|
return jsonResponse({ error: 'Query parameter required' }, 400);
|
|
}
|
|
const mockNewsResults = [
|
|
{
|
|
title: `Breaking News: ${queryParam} Update`,
|
|
url: `https://demo-news.com/${queryParam.toLowerCase().replace(/\s+/g, '-')}`,
|
|
description: `Latest news about ${queryParam} - this is a demo news search result showing how news search works in demo mode.`,
|
|
published_date: new Date().toISOString().split('T')[0],
|
|
language: 'English',
|
|
family_friendly: true,
|
|
type: 'news',
|
|
subtype: 'article',
|
|
},
|
|
{
|
|
title: `${queryParam} - Industry Report`,
|
|
url: `https://demo-industry.com/${queryParam.toLowerCase().replace(/\s+/g, '-')}`,
|
|
description: `Industry analysis and reports about ${queryParam}. This demo result shows how news search can include industry content.`,
|
|
published_date: new Date(Date.now() - 86400000).toISOString().split('T')[0],
|
|
language: 'English',
|
|
family_friendly: true,
|
|
type: 'news',
|
|
subtype: 'report',
|
|
},
|
|
];
|
|
return jsonResponse({
|
|
results: mockNewsResults,
|
|
news: { results: mockNewsResults },
|
|
query: { original: queryParam, display: queryParam },
|
|
mixed: { results: mockNewsResults },
|
|
});
|
|
}
|
|
|
|
// Removed items / trash
|
|
if (path.includes('/api/v1/trash') || path.includes('/api/v1/removed-items') || path === '/trash') {
|
|
return jsonResponse(generateMockRemovedItems());
|
|
}
|
|
|
|
// Update checking endpoints
|
|
if (path.includes('/api/updates/check')) {
|
|
return jsonResponse({
|
|
updateAvailable: false,
|
|
currentVersion: '1.0.0-demo',
|
|
latestVersion: '1.0.0-demo',
|
|
});
|
|
}
|
|
|
|
if (path.includes('/api/updates/progress')) {
|
|
return jsonResponse({
|
|
progress: 0,
|
|
downloading: false,
|
|
installing: false,
|
|
completed: false,
|
|
error: null,
|
|
});
|
|
}
|
|
|
|
return jsonResponse(
|
|
{
|
|
error: `No demo handler for ${method} ${path}`,
|
|
},
|
|
404
|
|
);
|
|
};
|
|
|
|
// Override global fetch for demo mode
|
|
export const initializeDemoMode = () => {
|
|
if (isDemoMode()) {
|
|
// Store original fetch to use for real API calls and restore later if needed
|
|
originalFetch = window.fetch;
|
|
window.fetch = demoFetch as typeof fetch;
|
|
return originalFetch;
|
|
}
|
|
return null;
|
|
};
|