2025-07-18 04:51:03 +00:00
|
|
|
|
|
|
|
|
'use server';
|
|
|
|
|
|
|
|
|
|
import db from '@/lib/db';
|
|
|
|
|
import { z } from 'zod';
|
|
|
|
|
import { revalidatePath } from 'next/cache';
|
|
|
|
|
import { redirect } from 'next/navigation';
|
|
|
|
|
|
|
|
|
|
export type Flow = {
|
|
|
|
|
id: number;
|
|
|
|
|
name: string;
|
|
|
|
|
description: string | null;
|
|
|
|
|
path: string;
|
|
|
|
|
createdAt: string;
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const flowSchema = z.object({
|
|
|
|
|
id: z.coerce.number().optional(),
|
|
|
|
|
name: z.string().min(1, 'Name is required'),
|
|
|
|
|
description: z.string().optional(),
|
|
|
|
|
path: z.string().min(1, 'Path is required').startsWith('/', { message: 'Path must start with /' }),
|
|
|
|
|
});
|
|
|
|
|
|
2025-07-18 04:52:21 +00:00
|
|
|
type State = {
|
|
|
|
|
success: boolean;
|
|
|
|
|
message: string;
|
|
|
|
|
errors?: z.ZodIssue[] | null;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-18 04:51:03 +00:00
|
|
|
export async function getFlows(): Promise<Flow[]> {
|
|
|
|
|
try {
|
|
|
|
|
const stmt = db.prepare(
|
|
|
|
|
'SELECT id, name, description, path, createdAt, updatedAt FROM flows ORDER BY createdAt DESC'
|
|
|
|
|
);
|
|
|
|
|
const flows = stmt.all() as Flow[];
|
|
|
|
|
return flows;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Failed to fetch flows:', error);
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function getFlow(id: number): Promise<Flow | null> {
|
|
|
|
|
try {
|
|
|
|
|
const stmt = db.prepare('SELECT * FROM flows WHERE id = ?');
|
|
|
|
|
const flow = stmt.get(id) as Flow | undefined;
|
|
|
|
|
return flow || null;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`Failed to fetch flow with id ${id}:`, error);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-18 04:52:21 +00:00
|
|
|
export async function saveFlow(prevState: State, formData: FormData): Promise<State> {
|
2025-07-18 04:51:03 +00:00
|
|
|
const validatedFields = flowSchema.safeParse(Object.fromEntries(formData.entries()));
|
|
|
|
|
|
|
|
|
|
if (!validatedFields.success) {
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
message: 'Invalid fields.',
|
2025-07-18 04:52:21 +00:00
|
|
|
errors: validatedFields.error.issues,
|
2025-07-18 04:51:03 +00:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { id, name, description, path } = validatedFields.data;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
if (id) {
|
2025-07-19 15:10:27 +00:00
|
|
|
// Check if path is unique before updating
|
|
|
|
|
const checkStmt = db.prepare('SELECT id FROM flows WHERE path = ? AND id != ?');
|
|
|
|
|
const existing = checkStmt.get(path, id);
|
|
|
|
|
if (existing) {
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
message: 'A flow with this path already exists.',
|
|
|
|
|
errors: [{ path: ['path'], message: 'A flow with this path already exists.', code: 'custom' }],
|
|
|
|
|
};
|
|
|
|
|
}
|
2025-07-18 04:51:03 +00:00
|
|
|
// Update existing flow
|
|
|
|
|
const stmt = db.prepare(
|
|
|
|
|
'UPDATE flows SET name = ?, description = ?, path = ?, updatedAt = CURRENT_TIMESTAMP WHERE id = ?'
|
|
|
|
|
);
|
|
|
|
|
stmt.run(name, description || null, path, id);
|
|
|
|
|
} else {
|
2025-09-01 06:56:28 +00:00
|
|
|
// Check if path is unique before inserting
|
|
|
|
|
const checkStmt = db.prepare('SELECT id FROM flows WHERE path = ?');
|
|
|
|
|
const existing = checkStmt.get(path);
|
|
|
|
|
if (existing) {
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
message: 'A flow with this path already exists.',
|
|
|
|
|
errors: [{ path: ['path'], message: 'A flow with this path already exists.', code: 'custom' }],
|
|
|
|
|
};
|
|
|
|
|
}
|
2025-07-18 04:51:03 +00:00
|
|
|
// Create new flow
|
|
|
|
|
const stmt = db.prepare(
|
|
|
|
|
'INSERT INTO flows (name, description, path) VALUES (?, ?, ?)'
|
|
|
|
|
);
|
|
|
|
|
stmt.run(name, description || null, path);
|
|
|
|
|
}
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
console.error('Failed to save flow:', error);
|
2025-07-18 04:52:21 +00:00
|
|
|
if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') {
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
message: 'A flow with this path already exists.',
|
|
|
|
|
errors: [{ path: ['path'], message: 'A flow with this path already exists.', code: 'custom' }],
|
|
|
|
|
};
|
|
|
|
|
}
|
2025-07-18 04:51:03 +00:00
|
|
|
return {
|
|
|
|
|
success: false,
|
2025-07-18 04:52:21 +00:00
|
|
|
message: 'An internal error occurred.',
|
2025-07-18 04:51:03 +00:00
|
|
|
errors: null,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
revalidatePath('/admin/flows');
|
|
|
|
|
redirect('/admin/flows');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function deleteFlow(id: number): Promise<{ success: boolean, message: string }> {
|
|
|
|
|
try {
|
2025-07-19 15:10:27 +00:00
|
|
|
// Prevent deletion of the default flow (ID 1)
|
|
|
|
|
if (id === 1) {
|
|
|
|
|
return { success: false, message: "The default flow cannot be deleted." };
|
|
|
|
|
}
|
2025-07-18 04:51:03 +00:00
|
|
|
const stmt = db.prepare('DELETE FROM flows WHERE id = ?');
|
|
|
|
|
stmt.run(id);
|
|
|
|
|
revalidatePath('/admin/flows');
|
|
|
|
|
return { success: true, message: 'Flow deleted successfully.' };
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Failed to delete flow:', error);
|
|
|
|
|
return { success: false, message: 'An internal error occurred.' };
|
|
|
|
|
}
|
|
|
|
|
}
|