Automated Action a8270cc5cb Complete rebuild: Node.js/Express/TypeScript WhatsApp AI task scheduling service
- Replaced Python/FastAPI implementation with Node.js/Express/TypeScript
- Added Prisma ORM with SQLite database
- Implemented JWT authentication with bcrypt password hashing
- Created comprehensive task management API with CRUD operations
- Integrated Twilio WhatsApp Business API for notifications
- Added OpenAI integration for intelligent task scheduling
- Implemented automated background jobs with node-cron
- Added comprehensive error handling and validation
- Structured logging with Winston
- Complete API documentation and setup instructions

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-27 16:50:54 +00:00

306 lines
7.5 KiB
TypeScript

import { Response } from 'express';
import { validate, taskCreationSchema, taskUpdateSchema } from '@/utils/validation';
import prisma from '@/utils/database';
import logger from '@/utils/logger';
import { AuthenticatedRequest, CreateTaskData, UpdateTaskData, ApiResponse } from '@/types';
import { suggestOptimalTime } from '@/services/aiService';
import { sendTaskReminder } from '@/services/whatsappService';
export const createTask = async (req: AuthenticatedRequest, res: Response) => {
try {
const taskData: CreateTaskData = validate(taskCreationSchema, req.body);
const userId = req.user!.id;
let aiSuggestedTime = null;
if (taskData.dueDate && !taskData.scheduledAt) {
aiSuggestedTime = await suggestOptimalTime(taskData.dueDate, taskData.priority || 'MEDIUM');
}
const task = await prisma.task.create({
data: {
title: taskData.title,
description: taskData.description,
priority: taskData.priority || 'MEDIUM',
dueDate: taskData.dueDate,
scheduledAt: taskData.scheduledAt,
aiSuggestedTime,
ownerId: userId,
},
include: {
owner: {
select: {
id: true,
email: true,
fullName: true,
}
}
}
});
logger.info(`Task created: ${task.title} by user ${userId}`);
return res.status(201).json({
success: true,
message: 'Task created successfully',
data: task
} as ApiResponse);
} catch (error) {
logger.error('Task creation error:', error);
return res.status(400).json({
success: false,
message: error instanceof Error ? error.message : 'Task creation failed'
} as ApiResponse);
}
};
export const getTasks = async (req: AuthenticatedRequest, res: Response) => {
try {
const userId = req.user!.id;
const { status, priority, page = '1', limit = '20' } = req.query;
const pageNum = parseInt(page as string);
const limitNum = parseInt(limit as string);
const skip = (pageNum - 1) * limitNum;
const where: any = { ownerId: userId };
if (status) where.status = status;
if (priority) where.priority = priority;
const [tasks, total] = await Promise.all([
prisma.task.findMany({
where,
orderBy: { createdAt: 'desc' },
skip,
take: limitNum,
include: {
owner: {
select: {
id: true,
email: true,
fullName: true,
}
}
}
}),
prisma.task.count({ where })
]);
return res.status(200).json({
success: true,
message: 'Tasks retrieved successfully',
data: {
tasks,
pagination: {
page: pageNum,
limit: limitNum,
total,
pages: Math.ceil(total / limitNum)
}
}
} as ApiResponse);
} catch (error) {
logger.error('Get tasks error:', error);
return res.status(500).json({
success: false,
message: 'Failed to retrieve tasks'
} as ApiResponse);
}
};
export const getTask = async (req: AuthenticatedRequest, res: Response) => {
try {
const taskId = parseInt(req.params.id);
const userId = req.user!.id;
const task = await prisma.task.findFirst({
where: {
id: taskId,
ownerId: userId
},
include: {
owner: {
select: {
id: true,
email: true,
fullName: true,
}
}
}
});
if (!task) {
return res.status(404).json({
success: false,
message: 'Task not found'
} as ApiResponse);
}
return res.status(200).json({
success: true,
message: 'Task retrieved successfully',
data: task
} as ApiResponse);
} catch (error) {
logger.error('Get task error:', error);
return res.status(500).json({
success: false,
message: 'Failed to retrieve task'
} as ApiResponse);
}
};
export const updateTask = async (req: AuthenticatedRequest, res: Response) => {
try {
const taskId = parseInt(req.params.id);
const userId = req.user!.id;
const updateData: UpdateTaskData = validate(taskUpdateSchema, req.body);
const existingTask = await prisma.task.findFirst({
where: {
id: taskId,
ownerId: userId
}
});
if (!existingTask) {
return res.status(404).json({
success: false,
message: 'Task not found'
} as ApiResponse);
}
const updatePayload: any = { ...updateData };
if (updateData.status === 'COMPLETED' && existingTask.status !== 'COMPLETED') {
updatePayload.completedAt = new Date();
}
const task = await prisma.task.update({
where: { id: taskId },
data: updatePayload,
include: {
owner: {
select: {
id: true,
email: true,
fullName: true,
}
}
}
});
logger.info(`Task updated: ${task.title} by user ${userId}`);
return res.status(200).json({
success: true,
message: 'Task updated successfully',
data: task
} as ApiResponse);
} catch (error) {
logger.error('Task update error:', error);
return res.status(400).json({
success: false,
message: error instanceof Error ? error.message : 'Task update failed'
} as ApiResponse);
}
};
export const deleteTask = async (req: AuthenticatedRequest, res: Response) => {
try {
const taskId = parseInt(req.params.id);
const userId = req.user!.id;
const task = await prisma.task.findFirst({
where: {
id: taskId,
ownerId: userId
}
});
if (!task) {
return res.status(404).json({
success: false,
message: 'Task not found'
} as ApiResponse);
}
await prisma.task.delete({
where: { id: taskId }
});
logger.info(`Task deleted: ${task.title} by user ${userId}`);
return res.status(200).json({
success: true,
message: 'Task deleted successfully'
} as ApiResponse);
} catch (error) {
logger.error('Task deletion error:', error);
return res.status(500).json({
success: false,
message: 'Failed to delete task'
} as ApiResponse);
}
};
export const sendReminder = async (req: AuthenticatedRequest, res: Response) => {
try {
const taskId = parseInt(req.params.id);
const userId = req.user!.id;
const task = await prisma.task.findFirst({
where: {
id: taskId,
ownerId: userId
},
include: {
owner: true
}
});
if (!task) {
return res.status(404).json({
success: false,
message: 'Task not found'
} as ApiResponse);
}
if (!task.owner.whatsappNumber) {
return res.status(400).json({
success: false,
message: 'WhatsApp number not configured'
} as ApiResponse);
}
const success = await sendTaskReminder(task.owner.whatsappNumber, task);
if (success) {
await prisma.task.update({
where: { id: taskId },
data: { whatsappReminderSent: true }
});
return res.status(200).json({
success: true,
message: 'Reminder sent successfully'
} as ApiResponse);
} else {
return res.status(500).json({
success: false,
message: 'Failed to send reminder'
} as ApiResponse);
}
} catch (error) {
logger.error('Send reminder error:', error);
return res.status(500).json({
success: false,
message: 'Failed to send reminder'
} as ApiResponse);
}
};