arcadia-suite-sv/server/automations/routes.ts

343 lines
12 KiB
TypeScript

import type { Express, Request, Response } from "express";
import { db } from "../../db/index";
import { automations, automationActions, automationLogs, scheduledTasks, insertAutomationSchema } from "@shared/schema";
import { eq, desc, and } from "drizzle-orm";
import { z } from "zod";
const automationServiceModule = async () => {
const { automationService } = await import("./service");
return automationService;
};
const createAutomationBodySchema = z.object({
name: z.string().min(1, "Nome é obrigatório"),
description: z.string().optional().nullable(),
triggerType: z.enum(["schedule", "webhook", "manual", "event"]),
triggerConfig: z.record(z.any()).optional().nullable(),
actions: z.array(z.object({
actionType: z.string(),
actionConfig: z.record(z.any()).optional().nullable(),
conditionConfig: z.record(z.any()).optional().nullable(),
})).optional(),
schedule: z.object({
cronExpression: z.string().optional().nullable(),
intervalMinutes: z.number().min(1).optional().nullable(),
}).optional(),
});
const updateAutomationBodySchema = z.object({
name: z.string().min(1).optional(),
description: z.string().optional().nullable(),
triggerType: z.enum(["schedule", "webhook", "manual", "event"]).optional(),
triggerConfig: z.record(z.any()).optional().nullable(),
isActive: z.union([z.boolean(), z.enum(["true", "false"])]).optional(),
actions: z.array(z.object({
actionType: z.string(),
actionConfig: z.record(z.any()).optional().nullable(),
conditionConfig: z.record(z.any()).optional().nullable(),
})).optional(),
schedule: z.object({
cronExpression: z.string().optional().nullable(),
intervalMinutes: z.number().min(1).optional().nullable(),
}).optional(),
});
function normalizeIsActive(value: any): string {
if (value === true || value === "true") return "true";
if (value === false || value === "false") return "false";
return "true";
}
export function registerAutomationRoutes(app: Express): void {
app.get("/api/automations", async (req: Request, res: Response) => {
try {
if (!req.isAuthenticated()) {
return res.status(401).json({ error: "Not authenticated" });
}
const userAutomations = await db.select()
.from(automations)
.where(eq(automations.userId, req.user!.id))
.orderBy(desc(automations.createdAt));
res.json(userAutomations);
} catch (error) {
console.error("Get automations error:", error);
res.status(500).json({ error: "Failed to get automations" });
}
});
app.get("/api/automations/:id", async (req: Request, res: Response) => {
try {
if (!req.isAuthenticated()) {
return res.status(401).json({ error: "Not authenticated" });
}
const automationId = parseInt(req.params.id);
if (isNaN(automationId)) {
return res.status(400).json({ error: "Invalid automation ID" });
}
const [automation] = await db.select()
.from(automations)
.where(and(eq(automations.id, automationId), eq(automations.userId, req.user!.id)));
if (!automation) {
return res.status(404).json({ error: "Automation not found" });
}
const actions = await db.select()
.from(automationActions)
.where(eq(automationActions.automationId, automationId))
.orderBy(automationActions.orderIndex);
const schedule = await db.select()
.from(scheduledTasks)
.where(eq(scheduledTasks.automationId, automationId));
res.json({ ...automation, actions, schedule: schedule[0] || null });
} catch (error) {
console.error("Get automation error:", error);
res.status(500).json({ error: "Failed to get automation" });
}
});
app.post("/api/automations", async (req: Request, res: Response) => {
try {
if (!req.isAuthenticated()) {
return res.status(401).json({ error: "Not authenticated" });
}
const parseResult = createAutomationBodySchema.safeParse(req.body);
if (!parseResult.success) {
return res.status(400).json({
error: "Dados inválidos",
details: parseResult.error.errors
});
}
const { name, description, triggerType, triggerConfig, actions, schedule } = parseResult.data;
const [automation] = await db.insert(automations).values({
userId: req.user!.id,
name,
description: description ?? null,
triggerType,
triggerConfig: triggerConfig ? JSON.stringify(triggerConfig) : null,
isActive: "true",
}).returning();
if (actions && actions.length > 0) {
for (let i = 0; i < actions.length; i++) {
await db.insert(automationActions).values({
automationId: automation.id,
orderIndex: i,
actionType: actions[i].actionType,
actionConfig: actions[i].actionConfig ? JSON.stringify(actions[i].actionConfig) : null,
conditionConfig: actions[i].conditionConfig ? JSON.stringify(actions[i].conditionConfig) : null,
});
}
}
if (triggerType === "schedule" && schedule) {
const now = new Date();
let nextRunAt: Date | null = null;
if (schedule.intervalMinutes) {
nextRunAt = new Date(now.getTime() + schedule.intervalMinutes * 60000);
}
await db.insert(scheduledTasks).values({
automationId: automation.id,
cronExpression: schedule.cronExpression ?? null,
intervalMinutes: schedule.intervalMinutes ?? null,
nextRunAt,
isActive: "true",
});
}
res.json(automation);
} catch (error) {
console.error("Create automation error:", error);
res.status(500).json({ error: "Failed to create automation" });
}
});
app.put("/api/automations/:id", async (req: Request, res: Response) => {
try {
if (!req.isAuthenticated()) {
return res.status(401).json({ error: "Not authenticated" });
}
const automationId = parseInt(req.params.id);
if (isNaN(automationId)) {
return res.status(400).json({ error: "Invalid automation ID" });
}
const parseResult = updateAutomationBodySchema.safeParse(req.body);
if (!parseResult.success) {
return res.status(400).json({
error: "Dados inválidos",
details: parseResult.error.errors
});
}
const { name, description, triggerType, triggerConfig, actions, schedule, isActive } = parseResult.data;
const [existing] = await db.select()
.from(automations)
.where(and(eq(automations.id, automationId), eq(automations.userId, req.user!.id)));
if (!existing) {
return res.status(404).json({ error: "Automation not found" });
}
const normalizedIsActive = isActive !== undefined ? normalizeIsActive(isActive) : existing.isActive;
const [updated] = await db.update(automations)
.set({
name: name ?? existing.name,
description: description !== undefined ? description : existing.description,
triggerType: triggerType ?? existing.triggerType,
triggerConfig: triggerConfig !== undefined
? (triggerConfig ? JSON.stringify(triggerConfig) : null)
: existing.triggerConfig,
isActive: normalizedIsActive,
updatedAt: new Date(),
})
.where(eq(automations.id, automationId))
.returning();
if (actions !== undefined) {
await db.delete(automationActions).where(eq(automationActions.automationId, automationId));
for (let i = 0; i < actions.length; i++) {
await db.insert(automationActions).values({
automationId: automationId,
orderIndex: i,
actionType: actions[i].actionType,
actionConfig: actions[i].actionConfig ? JSON.stringify(actions[i].actionConfig) : null,
conditionConfig: actions[i].conditionConfig ? JSON.stringify(actions[i].conditionConfig) : null,
});
}
}
const effectiveTriggerType = triggerType ?? existing.triggerType;
if (effectiveTriggerType === "schedule") {
await db.delete(scheduledTasks).where(eq(scheduledTasks.automationId, automationId));
if (schedule) {
const now = new Date();
let nextRunAt: Date | null = null;
if (schedule.intervalMinutes) {
nextRunAt = new Date(now.getTime() + schedule.intervalMinutes * 60000);
}
await db.insert(scheduledTasks).values({
automationId: automationId,
cronExpression: schedule.cronExpression ?? null,
intervalMinutes: schedule.intervalMinutes ?? null,
nextRunAt,
isActive: normalizedIsActive,
});
}
} else {
await db.delete(scheduledTasks).where(eq(scheduledTasks.automationId, automationId));
}
res.json(updated);
} catch (error) {
console.error("Update automation error:", error);
res.status(500).json({ error: "Failed to update automation" });
}
});
app.delete("/api/automations/:id", async (req: Request, res: Response) => {
try {
if (!req.isAuthenticated()) {
return res.status(401).json({ error: "Not authenticated" });
}
const automationId = parseInt(req.params.id);
if (isNaN(automationId)) {
return res.status(400).json({ error: "Invalid automation ID" });
}
const [existing] = await db.select()
.from(automations)
.where(and(eq(automations.id, automationId), eq(automations.userId, req.user!.id)));
if (!existing) {
return res.status(404).json({ error: "Automation not found" });
}
await db.delete(automations).where(eq(automations.id, automationId));
res.json({ success: true });
} catch (error) {
console.error("Delete automation error:", error);
res.status(500).json({ error: "Failed to delete automation" });
}
});
app.post("/api/automations/:id/run", async (req: Request, res: Response) => {
try {
if (!req.isAuthenticated()) {
return res.status(401).json({ error: "Not authenticated" });
}
const automationId = parseInt(req.params.id);
if (isNaN(automationId)) {
return res.status(400).json({ error: "Invalid automation ID" });
}
const [automation] = await db.select()
.from(automations)
.where(and(eq(automations.id, automationId), eq(automations.userId, req.user!.id)));
if (!automation) {
return res.status(404).json({ error: "Automation not found" });
}
const service = await automationServiceModule();
const result = await service.runAutomation(automationId, req.user!.id);
res.json(result);
} catch (error) {
console.error("Run automation error:", error);
res.status(500).json({ error: "Failed to run automation" });
}
});
app.get("/api/automations/:id/logs", async (req: Request, res: Response) => {
try {
if (!req.isAuthenticated()) {
return res.status(401).json({ error: "Not authenticated" });
}
const automationId = parseInt(req.params.id);
if (isNaN(automationId)) {
return res.status(400).json({ error: "Invalid automation ID" });
}
const [automation] = await db.select()
.from(automations)
.where(and(eq(automations.id, automationId), eq(automations.userId, req.user!.id)));
if (!automation) {
return res.status(404).json({ error: "Automation not found" });
}
const logs = await db.select()
.from(automationLogs)
.where(eq(automationLogs.automationId, automationId))
.orderBy(desc(automationLogs.startedAt))
.limit(50);
res.json(logs);
} catch (error) {
console.error("Get automation logs error:", error);
res.status(500).json({ error: "Failed to get logs" });
}
});
}