database-expert
Expert database architect specializing in schema design, query optimization, data modeling, and migration strategies. Japanese: データベースエキスパート
Generate Next.js Server Actions for form handling and data mutations.
> /plugin marketplace add Fujigo-Software/f5-framework-claudeHow it fires
How this agent gets triggered: by you, by Claude, or both.
Context preview
The summary Claude sees to decide when to auto-load this agent.
Generate Next.js Server Actions for form handling and data mutations.
Generate Next.js Server Actions for form handling and data mutations.
required: - name: string # Action name (camelCase) - entity: string # Entity to operate on optional: - operation: string # create | update | delete | toggle - auth_required: boolean # Requires authentication - validation: object # Zod schema definition - revalidate: array # Paths to revalidate - redirect_to: string # Redirect after success
lib/actions/
├── {entities}.ts # Actions for entity
└── types.ts # Shared action types (if needed)// lib/actions/products.ts
"use server";
import { revalidatePath, revalidateTag } from 'next/cache';
import { redirect } from 'next/navigation';
import { z } from 'zod';
import { db } from '@/lib/db';
import { auth } from '@/lib/auth';
// Schema definitions
const createProductSchema = z.object({
name: z.string().min(2, 'Name must be at least 2 characters').max(100),
description: z.string().max(1000).optional(),
price: z.coerce.number().min(0, 'Price must be positive'),
categoryId: z.string().uuid('Invalid category'),
});
const updateProductSchema = createProductSchema.partial();
// Action state type
export type ProductActionState = {
success: boolean;
message: string;
errors?: Record<string, string[]>;
data?: unknown;
};
// Initial state
const initialState: ProductActionState = {
success: false,
message: '',
};
// Create action
export async function createProduct(
prevState: ProductActionState,
formData: FormData
): Promise<ProductActionState> {
// Auth check
const session = await auth();
if (!session?.user) {
return { success: false, message: 'Unauthorized' };
}
// Validate
const validatedFields = createProductSchema.safeParse({
name: formData.get('name'),
description: formData.get('description'),
price: formData.get('price'),
categoryId: formData.get('categoryId'),
});
if (!validatedFields.success) {
return {
success: false,
message: 'Validation failed',
errors: validatedFields.error.flatten().fieldErrors,
};
}
try {
const product = await db.product.create({
data: {
...validatedFields.data,
userId: session.user.id,
slug: slugify(validatedFields.data.name),
},
});
revalidatePath('/products');
revalidateTag('products');
return {
success: true,
message: 'Product created successfully',
data: { id: product.id },
};
} catch (error) {
console.error('Create product error:', error);
return { success: false, message: 'Failed to create product' };
}
}
// Update action
export async function updateProduct(
id: string,
prevState: ProductActionState,
formData: FormData
): Promise<ProductActionState> {
const session = await auth();
if (!session?.user) {
return { success: false, message: 'Unauthorized' };
}
// Check ownership
const existing = await db.product.findUnique({
where: { id },
select: { userId: true },
});
if (!existing) {
return { success: false, message: 'Product not found' };
}
if (existing.userId !== session.user.id) {
return { success: false, message: 'Forbidden' };
}
// Validate
const validatedFields = updateProductSchema.safeParse({
name: formData.get('name') || undefined,
description: formData.get('description') || undefined,
price: formData.get('price') || undefined,
categoryId: formData.get('categoryId') || undefined,
});
if (!validatedFields.success) {
return {
success: false,
message: 'Validation failed',
errors: validatedFields.error.flatten().fieldErrors,
};
}
try {
await db.product.update({
where: { id },
data: validatedFields.data,
});
revalidatePath(`/products/${id}`);
revalidatePath('/products');
return { success: true, message: 'Product updated successfully' };
} catch (error) {
console.error('Update product error:', error);
return { success: false, message: 'Failed to update product' };
}
}
// Delete action
export async function deleteProduct(id: string): Promise<ProductActionState> {
const session = await auth();
if (!session?.user) {
return { success: false, message: 'Unauthorized' };
}
const product = await db.product.findUnique({
where: { id },
select: { userId: true },
});
if (!product) {
return { success: false, message: 'Product not found' };
}
if (product.userId !== session.user.id) {
return { success: false, message: 'Forbidden' };
}
try {
await db.product.delete({ where: { id } });
revalidatePath('/products');
revalidateTag('products');
return { success: true, message: 'Product deleted successfully' };
} catch (error) {
console.error('Delete product error:', error);
return { success: false, message: 'Failed to delete product' };
}
}
// Helper
function slugify(text: string): string {
return text
.toLowerCase()
.replace(/[^\w\s-]/g, '')
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.trim();
}// lib/actions/products.ts (continued)
export async function toggleProductStatus(
id: string,
status: 'active' | 'draft' | 'archived'
): Promise<ProductActionState> {
const session = await auth();
if (!session?.user) {
return { success: false, message: 'Unauthorized' };
}
try {
await db.product.update({
where: {
id,
userId: session.user.id,
},
datRepo: Fujigo-Software/f5-framework-claude
Expert database architect specializing in schema design, query optimization, data modeling, and migration strategies. Japanese: データベースエキスパート
Expert DevOps architect specializing in CI/CD pipelines, infrastructure as code, containerization, and monitoring. Japanese: DevOpsアーキテクト
Mobile app architecture specialist. iOS, Android, React Native, Flutter.
Backend architecture specialist. Microservices, APIs, databases.
Frontend architecture specialist. React, Vue, Angular, Next.js.