Fondation
This commit is contained in:
parent
0bfa26838a
commit
c341468996
@ -68,7 +68,6 @@ async function getLeantimeUserId(email: string): Promise<number | null> {
|
|||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
logger.debug('[LEANTIME_TASKS] Found Leantime user', {
|
logger.debug('[LEANTIME_TASKS] Found Leantime user', {
|
||||||
id: user.id,
|
|
||||||
emailHash: Buffer.from(email.toLowerCase()).toString('base64').slice(0, 12),
|
emailHash: Buffer.from(email.toLowerCase()).toString('base64').slice(0, 12),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -102,7 +101,7 @@ export async function GET(request: NextRequest) {
|
|||||||
const cachedTasks = await getCachedTasksData(session.user.id);
|
const cachedTasks = await getCachedTasksData(session.user.id);
|
||||||
if (cachedTasks) {
|
if (cachedTasks) {
|
||||||
logger.debug('[LEANTIME_TASKS] Using cached tasks data', {
|
logger.debug('[LEANTIME_TASKS] Using cached tasks data', {
|
||||||
userId: session.user.id,
|
emailHash: Buffer.from(session.user.email.toLowerCase()).toString('base64').slice(0, 12),
|
||||||
taskCount: Array.isArray(cachedTasks) ? cachedTasks.length : undefined,
|
taskCount: Array.isArray(cachedTasks) ? cachedTasks.length : undefined,
|
||||||
});
|
});
|
||||||
return NextResponse.json(cachedTasks);
|
return NextResponse.json(cachedTasks);
|
||||||
@ -121,8 +120,8 @@ export async function GET(request: NextRequest) {
|
|||||||
return NextResponse.json({ error: "User not found in Leantime" }, { status: 404 });
|
return NextResponse.json({ error: "User not found in Leantime" }, { status: 404 });
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug('[LEANTIME_TASKS] Fetching tasks for Leantime user ID', {
|
logger.debug('[LEANTIME_TASKS] Fetching tasks for Leantime user', {
|
||||||
userId,
|
emailHash: Buffer.from(session.user.email.toLowerCase()).toString('base64').slice(0, 12),
|
||||||
});
|
});
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|||||||
@ -115,7 +115,6 @@ async function getTwentyCrmUserId(email: string): Promise<string | null> {
|
|||||||
|
|
||||||
if (member) {
|
if (member) {
|
||||||
logger.debug('[TWENTY_CRM_TASKS] Found workspace member', {
|
logger.debug('[TWENTY_CRM_TASKS] Found workspace member', {
|
||||||
memberId: member.node.id,
|
|
||||||
emailHash: Buffer.from(email.toLowerCase()).toString('base64').slice(0, 12),
|
emailHash: Buffer.from(email.toLowerCase()).toString('base64').slice(0, 12),
|
||||||
});
|
});
|
||||||
return member.node.id;
|
return member.node.id;
|
||||||
@ -230,12 +229,11 @@ async function fetchTwentyTasks(userId?: string): Promise<TwentyTask[]> {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log raw response for debugging (using error level to ensure visibility)
|
// Log raw response metadata (no sensitive data)
|
||||||
logger.error('[TWENTY_CRM_TASKS] Raw GraphQL response', {
|
logger.debug('[TWENTY_CRM_TASKS] Raw GraphQL response received', {
|
||||||
hasData: !!data.data,
|
hasData: !!data.data,
|
||||||
dataKeys: data.data ? Object.keys(data.data) : [],
|
dataKeys: data.data ? Object.keys(data.data) : [],
|
||||||
tasksEdgesCount: data.data?.tasks?.edges?.length || 0,
|
tasksEdgesCount: data.data?.tasks?.edges?.length || 0,
|
||||||
sampleResponse: JSON.stringify(data.data).substring(0, 1000),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Try different possible response structures
|
// Try different possible response structures
|
||||||
@ -270,21 +268,12 @@ async function fetchTwentyTasks(userId?: string): Promise<TwentyTask[]> {
|
|||||||
if (!activitiesData) {
|
if (!activitiesData) {
|
||||||
logger.warn('[TWENTY_CRM_TASKS] Unexpected response format from Twenty CRM', {
|
logger.warn('[TWENTY_CRM_TASKS] Unexpected response format from Twenty CRM', {
|
||||||
dataKeys: Object.keys(data.data || {}),
|
dataKeys: Object.keys(data.data || {}),
|
||||||
fullData: JSON.stringify(data.data).substring(0, 2000),
|
|
||||||
});
|
});
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug('[TWENTY_CRM_TASKS] Activities data extracted', {
|
logger.debug('[TWENTY_CRM_TASKS] Activities data extracted', {
|
||||||
count: activitiesData.length,
|
count: activitiesData.length,
|
||||||
sample: activitiesData.length > 0 ? {
|
|
||||||
firstTask: {
|
|
||||||
id: activitiesData[0]?.node?.id || activitiesData[0]?.id,
|
|
||||||
title: activitiesData[0]?.node?.title || activitiesData[0]?.title,
|
|
||||||
status: activitiesData[0]?.node?.status || activitiesData[0]?.status,
|
|
||||||
dueAt: activitiesData[0]?.node?.dueAt || activitiesData[0]?.dueAt,
|
|
||||||
}
|
|
||||||
} : null,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Transform Twenty CRM tasks to match our Task interface
|
// Transform Twenty CRM tasks to match our Task interface
|
||||||
@ -318,37 +307,20 @@ async function fetchTwentyTasks(userId?: string): Promise<TwentyTask[]> {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Log all tasks before filtering for debugging (using error level to ensure visibility)
|
// Log task count before filtering (no sensitive data)
|
||||||
logger.error('[TWENTY_CRM_TASKS] All tasks before filtering', {
|
logger.debug('[TWENTY_CRM_TASKS] Tasks before filtering', {
|
||||||
count: allTasks.length,
|
count: allTasks.length,
|
||||||
tasks: allTasks.map(t => ({
|
|
||||||
id: t.id,
|
|
||||||
title: t.title,
|
|
||||||
status: t.status,
|
|
||||||
dueAt: t.dueAt,
|
|
||||||
assigneeId: t.assigneeId,
|
|
||||||
hasDueDate: !!t.dueAt,
|
|
||||||
})),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Filter by assignee if userId is provided
|
// Filter by assignee if userId is provided
|
||||||
let filteredByAssignee = allTasks;
|
let filteredByAssignee = allTasks;
|
||||||
if (userId) {
|
if (userId) {
|
||||||
filteredByAssignee = allTasks.filter((task: TwentyTask) => {
|
filteredByAssignee = allTasks.filter((task: TwentyTask) => {
|
||||||
const isAssignedToUser = task.assigneeId === userId;
|
return task.assigneeId === userId;
|
||||||
if (!isAssignedToUser) {
|
|
||||||
logger.debug('[TWENTY_CRM_TASKS] Filtering out task not assigned to user', {
|
|
||||||
id: task.id,
|
|
||||||
assigneeId: task.assigneeId,
|
|
||||||
userId,
|
|
||||||
});
|
});
|
||||||
}
|
logger.debug('[TWENTY_CRM_TASKS] Tasks after assignee filter', {
|
||||||
return isAssignedToUser;
|
|
||||||
});
|
|
||||||
logger.error('[TWENTY_CRM_TASKS] Tasks after assignee filter', {
|
|
||||||
before: allTasks.length,
|
before: allTasks.length,
|
||||||
after: filteredByAssignee.length,
|
after: filteredByAssignee.length,
|
||||||
userId,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -363,12 +335,10 @@ async function fetchTwentyTasks(userId?: string): Promise<TwentyTask[]> {
|
|||||||
.filter((task: TwentyTask) => {
|
.filter((task: TwentyTask) => {
|
||||||
// Filter: only overdue tasks (dueAt < today) and not completed
|
// Filter: only overdue tasks (dueAt < today) and not completed
|
||||||
if (task.status === 'Done') {
|
if (task.status === 'Done') {
|
||||||
logger.debug('[TWENTY_CRM_TASKS] Filtering out Done task', { id: task.id, status: task.status });
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!task.dueAt) {
|
if (!task.dueAt) {
|
||||||
logger.debug('[TWENTY_CRM_TASKS] Filtering out task without due date', { id: task.id });
|
|
||||||
return false; // Exclude tasks without due date
|
return false; // Exclude tasks without due date
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -386,14 +356,6 @@ async function fetchTwentyTasks(userId?: string): Promise<TwentyTask[]> {
|
|||||||
(taskYear === todayYear && taskMonth < todayMonth) ||
|
(taskYear === todayYear && taskMonth < todayMonth) ||
|
||||||
(taskYear === todayYear && taskMonth === todayMonth && taskDay <= todayDay);
|
(taskYear === todayYear && taskMonth === todayMonth && taskDay <= todayDay);
|
||||||
|
|
||||||
logger.error('[TWENTY_CRM_TASKS] Task date check', {
|
|
||||||
id: task.id,
|
|
||||||
dueAt: task.dueAt,
|
|
||||||
taskDate: `${taskYear}-${String(taskMonth + 1).padStart(2, '0')}-${String(taskDay).padStart(2, '0')}`,
|
|
||||||
todayDate: `${todayYear}-${String(todayMonth + 1).padStart(2, '0')}-${String(todayDay).padStart(2, '0')}`,
|
|
||||||
isOverdueOrDueToday,
|
|
||||||
});
|
|
||||||
|
|
||||||
return isOverdueOrDueToday; // Include overdue tasks and tasks due today
|
return isOverdueOrDueToday; // Include overdue tasks and tasks due today
|
||||||
})
|
})
|
||||||
.sort((a: TwentyTask, b: TwentyTask) => {
|
.sort((a: TwentyTask, b: TwentyTask) => {
|
||||||
@ -406,12 +368,6 @@ async function fetchTwentyTasks(userId?: string): Promise<TwentyTask[]> {
|
|||||||
|
|
||||||
logger.debug('[TWENTY_CRM_TASKS] Tasks after filtering', {
|
logger.debug('[TWENTY_CRM_TASKS] Tasks after filtering', {
|
||||||
count: tasks.length,
|
count: tasks.length,
|
||||||
tasks: tasks.map(t => ({
|
|
||||||
id: t.id,
|
|
||||||
title: t.title,
|
|
||||||
status: t.status,
|
|
||||||
dueAt: t.dueAt,
|
|
||||||
})),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.debug('[TWENTY_CRM_TASKS] Successfully fetched tasks from Twenty CRM', {
|
logger.debug('[TWENTY_CRM_TASKS] Successfully fetched tasks from Twenty CRM', {
|
||||||
@ -463,7 +419,6 @@ export async function GET(request: NextRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.debug('[TWENTY_CRM_TASKS] Found Twenty CRM user ID', {
|
logger.debug('[TWENTY_CRM_TASKS] Found Twenty CRM user ID', {
|
||||||
userId: twentyCrmUserId,
|
|
||||||
emailHash: Buffer.from(session.user.email.toLowerCase()).toString('base64').slice(0, 12),
|
emailHash: Buffer.from(session.user.email.toLowerCase()).toString('base64').slice(0, 12),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
43
lib/redis.ts
43
lib/redis.ts
@ -1,5 +1,6 @@
|
|||||||
import Redis from 'ioredis';
|
import Redis from 'ioredis';
|
||||||
import CryptoJS from 'crypto-js';
|
import CryptoJS from 'crypto-js';
|
||||||
|
import { logger } from './logger';
|
||||||
|
|
||||||
// Initialize Redis client
|
// Initialize Redis client
|
||||||
let redisClient: Redis | null = null;
|
let redisClient: Redis | null = null;
|
||||||
@ -518,9 +519,14 @@ export async function cacheCalendarData(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await redis.set(key, JSON.stringify(data), 'EX', TTL.CALENDAR);
|
await redis.set(key, JSON.stringify(data), 'EX', TTL.CALENDAR);
|
||||||
console.log(`Calendar data cached for user ${userId}`);
|
logger.debug('[REDIS] Calendar data cached', {
|
||||||
|
userIdHash: Buffer.from(userId).toString('base64').slice(0, 12),
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error caching calendar data for user ${userId}:`, error);
|
logger.error('[REDIS] Error caching calendar data', {
|
||||||
|
userIdHash: Buffer.from(userId).toString('base64').slice(0, 12),
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -641,9 +647,14 @@ export async function cacheTasksData(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await redis.set(key, JSON.stringify(data), 'EX', TTL.TASKS);
|
await redis.set(key, JSON.stringify(data), 'EX', TTL.TASKS);
|
||||||
console.log(`Tasks data cached for user ${userId}`);
|
logger.debug('[REDIS] Tasks data cached', {
|
||||||
|
userIdHash: Buffer.from(userId).toString('base64').slice(0, 12),
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error caching tasks data for user ${userId}:`, error);
|
logger.error('[REDIS] Error caching tasks data', {
|
||||||
|
userIdHash: Buffer.from(userId).toString('base64').slice(0, 12),
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -664,7 +675,10 @@ export async function getCachedTasksData(
|
|||||||
|
|
||||||
return JSON.parse(cachedData);
|
return JSON.parse(cachedData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error getting cached tasks data for user ${userId}:`, error);
|
logger.error('[REDIS] Error getting cached tasks data', {
|
||||||
|
userIdHash: Buffer.from(userId).toString('base64').slice(0, 12),
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
});
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -680,9 +694,14 @@ export async function invalidateTasksCache(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await redis.del(key);
|
await redis.del(key);
|
||||||
console.log(`Tasks cache invalidated for user ${userId}`);
|
logger.debug('[REDIS] Tasks cache invalidated', {
|
||||||
|
userIdHash: Buffer.from(userId).toString('base64').slice(0, 12),
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error invalidating tasks cache for user ${userId}:`, error);
|
logger.error('[REDIS] Error invalidating tasks cache', {
|
||||||
|
userIdHash: Buffer.from(userId).toString('base64').slice(0, 12),
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -698,10 +717,14 @@ export async function cacheMessagesData(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await redis.set(key, JSON.stringify(data), 'EX', TTL.MESSAGES);
|
await redis.set(key, JSON.stringify(data), 'EX', TTL.MESSAGES);
|
||||||
// Debug-only; safe to be silent in production
|
logger.debug('[REDIS] Messages data cached', {
|
||||||
// logger.debug('[REDIS] Messages data cached', { userId });
|
userIdHash: Buffer.from(userId).toString('base64').slice(0, 12),
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error caching messages data for user ${userId}:`, error);
|
logger.error('[REDIS] Error caching messages data', {
|
||||||
|
userIdHash: Buffer.from(userId).toString('base64').slice(0, 12),
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user