| name | create-server-action |
| description | Cria Server Actions com autenticação, validação Zod e operações de banco de dados usando Drizzle ORM. Use quando precisar criar ou modificar Server Actions seguindo padrões de segurança Bewear. |
| tools | Read, Write, Edit, Glob, Grep, Bash |
Create Server Action Skill
Esta skill cria Server Actions seguindo os padrões de segurança e arquitetura do projeto Bewear.
Quando Usar
- Criar novas Server Actions
- Adicionar operações de banco de dados
- Implementar lógica de negócio no servidor
- Modificar Server Actions existentes
Regras Obrigatórias
1. Estrutura de Arquivos
SEMPRE crie dois arquivos na pasta src/actions/nome-action/:
src/actions/add-cart-product/
├── index.ts (Server Action)
└── schema.ts (Validação Zod)
Arquivo de referência obrigatório: src/actions/add-cart-product/
2. schema.ts - Validação
import { z } from "zod";
// 1. Definir schema Zod
export const addCartProductSchema = z.object({
productVariantId: z.uuid(),
quantity: z.number().min(1),
});
// 2. Exportar tipo inferido
export type AddCartProductSchema = z.infer<typeof addCartProductSchema>;
3. index.ts - Server Action
Template obrigatório com verificação de segurança:
"use server";
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
import { revalidatePath } from "next/cache";
import { nomeSchema, NomeSchema } from "./schema";
import { db } from "@/db";
import { nomeTable } from "@/db/schema";
import { eq } from "drizzle-orm";
export async function nomeAction(data: NomeSchema) {
// 1. VALIDAR DADOS (obrigatório)
nomeSchema.parse(data);
// 2. VERIFICAR AUTENTICAÇÃO (obrigatório)
const session = await auth.api.getSession({
headers: await headers(),
});
if (!session?.user) {
throw new Error("Unauthorized");
}
// 3. OPERAÇÕES DE BANCO DE DADOS
const result = await db.query.nomeTable.findFirst({
where: eq(nomeTable.userId, session.user.id),
});
if (!result) {
throw new Error("Resource not found");
}
// 4. VALIDAR OWNERSHIP (quando necessário)
if (result.userId !== session.user.id) {
throw new Error("Unauthorized");
}
// 5. EXECUTAR LÓGICA
await db.insert(nomeTable).values({
userId: session.user.id,
});
// 6. REVALIDAR CACHE (se necessário)
revalidatePath("/route");
// 7. RETORNAR RESULTADO
return result;
}
4. Verificação de Segurança (CRÍTICO)
SEMPRE inclua esta verificação em TODAS as Server Actions:
const session = await auth.api.getSession({
headers: await headers(),
});
if (!session?.user) {
throw new Error("Unauthorized");
}
Arquivo de referência: src/actions/add-cart-product/index.ts (linhas 14-20)
5. Validação de Ownership
Quando o recurso pertence a um usuário específico:
const order = await db.query.orderTable.findFirst({
where: eq(orderTable.id, orderId),
});
if (!order) {
throw new Error("Order not found");
}
if (order.userId !== session.user.id) {
throw new Error("Unauthorized");
}
Arquivo de referência: src/actions/create-checkout-session-stripe/index.ts (linhas 33-45)
6. Operações Drizzle ORM
Query (SELECT):
// Find First
const item = await db.query.tableName.findFirst({
where: eq(tableName.id, itemId),
with: {
relatedTable: true,
},
});
// Find Many
const items = await db.query.tableName.findMany({
where: eq(tableName.userId, session.user.id),
with: {
relatedTable: true,
},
});
Insert:
// Insert single
await db.insert(tableName).values({
userId: session.user.id,
field: data.field,
});
// Insert with returning
const [newItem] = await db.insert(tableName).values({
userId: session.user.id,
}).returning();
const itemId = newItem.id;
Update:
await db.update(tableName)
.set({
field: newValue,
})
.where(eq(tableName.id, itemId));
Delete:
await db.delete(tableName)
.where(eq(tableName.id, itemId));
7. Transactions
Para operações múltiplas que precisam ser atômicas:
await db.transaction(async (tx) => {
const [order] = await tx.insert(orderTable).values({
userId: session.user.id,
totalPriceInCents: total,
}).returning();
await tx.insert(orderItemTable).values({
orderId: order.id,
productVariantId: productId,
});
await tx.delete(cartTable).where(eq(cartTable.userId, session.user.id));
});
Arquivo de referência: src/actions/finish-purchase/index.ts (linhas 43-82)
8. Cache Revalidation
SEMPRE revalide o cache após mutações:
import { revalidatePath } from "next/cache";
// Revalidar rota específica
revalidatePath("/cart/identification");
// Revalidar layout completo
revalidatePath("/", "layout");
// Revalidar múltiplas rotas
revalidatePath("/cart/identification");
revalidatePath("/cart/confirmation");
9. Error Handling
// Erros de validação
if (!data.field) {
throw new Error("Field is required");
}
// Erros de autorização
if (!session?.user) {
throw new Error("Unauthorized");
}
// Erros de recurso não encontrado
if (!resource) {
throw new Error("Resource not found");
}
// Erros de ownership
if (resource.userId !== session.user.id) {
throw new Error("Unauthorized");
}
10. Imports Necessários
"use server";
// Autenticação
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
// Cache
import { revalidatePath } from "next/cache";
// Validação
import { nomeSchema, NomeSchema } from "./schema";
// Database
import { db } from "@/db";
import { tableName } from "@/db/schema";
import { eq, and, or } from "drizzle-orm";
Schemas Drizzle
Ver schema completo: src/db/schema.ts
Principais tabelas:
userTable- UsuárioscategoryTable- CategoriasproductTable- ProdutosproductVariantTable- Variantes de produtoscartTable- CarrinhoscartItemTable- Itens do carrinhoshippingAddressTable- Endereços de entregaorderTable- PedidosorderItemTable- Itens do pedido
Operadores Drizzle ORM
import { eq, and, or, gt, gte, lt, lte, like, inArray } from "drizzle-orm";
// Igual
where: eq(table.id, value)
// E lógico
where: and(
eq(table.userId, userId),
eq(table.status, "active")
)
// Ou lógico
where: or(
eq(table.status, "pending"),
eq(table.status, "processing")
)
// Maior que
where: gt(table.price, 1000)
// In array
where: inArray(table.status, ["pending", "paid"])
Padrões Comuns
Buscar ou Criar
let cart = await db.query.cartTable.findFirst({
where: eq(cartTable.userId, session.user.id),
});
if (!cart) {
const [newCart] = await db.insert(cartTable).values({
userId: session.user.id,
}).returning();
cart = newCart;
}
Incrementar Quantidade
const cartItem = await db.query.cartItemTable.findFirst({
where: and(
eq(cartItemTable.cartId, cartId),
eq(cartItemTable.productVariantId, productVariantId)
),
});
if (cartItem) {
await db.update(cartItemTable).set({
quantity: cartItem.quantity + quantity,
}).where(eq(cartItemTable.id, cartItem.id));
} else {
await db.insert(cartItemTable).values({
cartId,
productVariantId,
quantity,
});
}
Validações Zod Comuns para Server Actions
// UUID
productVariantId: z.uuid()
// String
name: z.string().min(1, "Required")
// Number
quantity: z.number().min(1)
// Enum
status: z.enum(["pending", "paid", "cancelled"])
// Object nested
address: z.object({
street: z.string(),
number: z.string(),
})
Arquivos de Referência
- Server Action simples:
src/actions/add-cart-product/index.ts - Server Action com ownership:
src/actions/create-checkout-session-stripe/index.ts - Server Action com transaction:
src/actions/finish-purchase/index.ts - Schema reference:
src/db/schema.ts
Processo de Criação
- Criar pasta:
src/actions/nome-action/ - Criar schema.ts: Definir validação Zod
- Criar index.ts: Implementar Server Action
- Adicionar segurança: Session check obrigatório
- Implementar lógica: Operações de banco de dados
- Validar ownership: Se necessário
- Revalidar cache: Se houver mutação
- Testar: Verificar autorização e validação
Checklist de Segurança
- "use server" no topo do arquivo
- Schema Zod definido em schema.ts
- Validação com schema.parse(data)
- Session check com auth.api.getSession()
- Verificação if (!session?.user)
- Validação de ownership quando necessário
- Error handling apropriado
- revalidatePath após mutações