refactor(recipients): move mutations into use cases

This commit is contained in:
2026-06-04 22:12:36 +02:00
parent f48ccb8c50
commit 24d2d59bbc
7 changed files with 282 additions and 159 deletions
+76
View File
@@ -0,0 +1,76 @@
"use server"
import { revalidatePath } from "next/cache"
import {
type CreateRecipientFormType,
createRecipientSchema,
type UpdateRecipientFormType,
updateRecipientSchema,
} from "@/schemas/recipient.schema"
import {
createRecipientUseCase,
updateRecipientUseCase,
} from "@/use-cases/recipient.use-cases"
export async function createNewRecipient(formData: CreateRecipientFormType) {
const validatedFields = createRecipientSchema.safeParse(formData)
if (!validatedFields.success) {
return {
success: false,
errors: validatedFields.error.flatten().fieldErrors,
}
}
try {
const result = await createRecipientUseCase(validatedFields.data)
if (!result.success) {
return result
}
revalidatePath("/recipients")
return {
success: true,
message: "Recipient created successfully",
}
} catch (error) {
console.error("Database error:", error)
return {
message: "Failed to create recipient",
}
}
}
export async function updateRecipient(formData: UpdateRecipientFormType) {
const validatedFields = updateRecipientSchema.safeParse(formData)
if (!validatedFields.success) {
return {
success: false,
errors: validatedFields.error.flatten().fieldErrors,
}
}
try {
const result = await updateRecipientUseCase(validatedFields.data)
if (!result.success) {
return result
}
revalidatePath("/recipients")
return {
success: true,
message: "Recipient updated successfully",
}
} catch (error) {
console.error("Database error:", error)
return {
message: "Failed to update recipient",
}
}
}
@@ -4,19 +4,18 @@ import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from "next/navigation"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { SubmitButton } from "@/components/forms/submitButton"
import { RecipientDepartment } from "@/generated/prisma/client"
import {
createNewRecipient,
updateRecipient,
} from "@/lib/actions/recipient.actions"
} from "@/actions/recipient.actions"
import { SubmitButton } from "@/components/forms/submitButton"
import { RECIPIENT_DEPARTMENTS } from "@/lib/constants"
import {
type CreateRecipientFormType,
recipientSchema,
type UpdateRecipientFormType,
} from "@/lib/schemas/recipients.schemas"
import type { Recipient } from "@/lib/types"
} from "@/schemas/recipient.schema"
import type { Recipient } from "@/types"
interface RecipientFormProps {
initialData?: Recipient
@@ -130,7 +129,7 @@ export default function RecipientForm({
className="w-full rounded-lg border px-4 py-2"
>
<option value="">Select a department</option>
{Object.keys(RecipientDepartment).map((department) => (
{Object.keys(RECIPIENT_DEPARTMENTS).map((department) => (
<option key={department} value={department}>
{department}
</option>
-138
View File
@@ -1,138 +0,0 @@
"use server"
import { revalidatePath } from "next/cache"
import prisma from "@/lib/prisma"
import {
type CreateRecipientFormType,
createRecipientSchema,
type UpdateRecipientFormType,
updateRecipientSchema,
} from "@/lib/schemas/recipients.schemas"
import { RecipientService } from "@/services/recipient.service"
export async function createNewRecipient(formData: CreateRecipientFormType) {
const validatedFields = createRecipientSchema.safeParse(formData)
if (!validatedFields.success) {
return {
success: false,
errors: validatedFields.error.flatten().fieldErrors,
}
}
const { username, firstName, lastName, department, email, phone } =
validatedFields.data
try {
const existingRecipientUsername =
await RecipientService.findByUsername(username)
if (existingRecipientUsername) {
return {
success: false,
errors: {
username: ["Username already exists"],
},
}
}
const existingRecipientEmail = await RecipientService.findByEmail(
email || "",
)
if (existingRecipientEmail) {
return {
success: false,
errors: {
email: ["Email already exists"],
},
}
}
await RecipientService.create({
username: username || (firstName[0] + lastName).toLowerCase(),
firstName,
lastName,
department: department,
email: email || null,
phone: phone || null,
})
revalidatePath("/recipients")
return {
success: true,
message: "Recipient created successfully",
}
} catch (error) {
console.error("Database error:", error)
return {
message: "Failed to create recipient",
}
}
}
export async function updateRecipient(formData: UpdateRecipientFormType) {
const validatedFields = updateRecipientSchema.safeParse(formData)
if (!validatedFields.success) {
return {
success: false,
errors: validatedFields.error.flatten().fieldErrors,
}
}
const { id, username, firstName, lastName, department, email, phone } =
validatedFields.data
try {
const existingRecipient = await RecipientService.findByUsername(username)
if (existingRecipient && existingRecipient.id !== id) {
return {
success: false,
errors: {
username: ["Username already exists"],
},
}
}
const existingRecipientEmail = await RecipientService.findByEmail(
email || "",
)
if (existingRecipientEmail && existingRecipientEmail.id !== id) {
return {
success: false,
errors: {
email: ["Email already exists"],
},
}
}
await prisma.recipient.update({
where: { id },
data: {
username,
firstName,
lastName,
department: department,
email: email || null,
phone: phone || null,
},
})
revalidatePath("/recipients")
return {
success: true,
message: "Recipient updated successfully",
}
} catch (error) {
console.error("Database error:", error)
return {
message: "Failed to update recipient",
}
}
}
+11
View File
@@ -8,6 +8,17 @@ export const SIGN_IN_URL = "/login"
export const TOKEN_EXPIRATION_SECONDS = 60 * 60 * 2 // 2 hour
export const RECIPIENT_DEPARTMENTS = {
IT: "IT",
ENGINEERING: "ENGINEERING",
LOGISTICS: "LOGISTICS",
TRAFFIC: "TRAFFIC",
DRIVER: "DRIVER",
ADMINISTRATION: "ADMINISTRATION",
SALES: "SALES",
OTHER: "OTHER",
} as const
export const ITEM_STATUS = {
AVAILABLE: "AVAILABLE",
ASSIGNED: "ASSIGNED",
@@ -5,15 +5,15 @@ export const recipientSchema = z.object({
username: z
.string()
.min(1, {
error: "Username is required"
error: "Username is required",
})
.nonempty("Username is required"),
firstName: z.string().min(1, {
error: "First name is required"
}),
error: "First name is required",
}),
lastName: z.string().min(1, {
error: "Last name is required"
}),
error: "Last name is required",
}),
department: z.enum(
[
"IT",
@@ -26,7 +26,7 @@ export const recipientSchema = z.object({
"OTHER",
],
{
error: "Department is required"
error: "Department is required",
},
),
email: z.string().optional().nullable(),
+28 -8
View File
@@ -40,19 +40,39 @@ export const RecipientService = {
return prisma.recipient.count()
},
findById: async (id: string): Promise<Recipient | null> => {
return prisma.recipient.findUnique({ where: { id } })
findById: async (
id: string,
db: Prisma.TransactionClient | typeof prisma = prisma,
): Promise<Recipient | null> => {
return db.recipient.findUnique({ where: { id } })
},
findByUsername: async (username: string): Promise<Recipient | null> => {
return prisma.recipient.findUnique({ where: { username } })
findByUsername: async (
username: string,
db: Prisma.TransactionClient | typeof prisma = prisma,
): Promise<Recipient | null> => {
return db.recipient.findUnique({ where: { username } })
},
findByEmail: async (email: string): Promise<Recipient | null> => {
return prisma.recipient.findUnique({ where: { email } })
findByEmail: async (
email: string,
db: Prisma.TransactionClient | typeof prisma = prisma,
): Promise<Recipient | null> => {
return db.recipient.findUnique({ where: { email } })
},
create: async (data: Prisma.RecipientCreateInput): Promise<Recipient> => {
return prisma.recipient.create({ data })
create: async (
data: Prisma.RecipientCreateInput,
db: Prisma.TransactionClient | typeof prisma = prisma,
): Promise<Recipient> => {
return db.recipient.create({ data })
},
update: async (
id: string,
data: Prisma.RecipientUpdateInput,
db: Prisma.TransactionClient | typeof prisma = prisma,
): Promise<Recipient> => {
return db.recipient.update({ where: { id }, data })
},
}
+155
View File
@@ -0,0 +1,155 @@
import { Prisma } from "@/generated/prisma/client"
import prisma from "@/lib/prisma"
import type {
CreateRecipientFormType,
UpdateRecipientFormType,
} from "@/schemas/recipient.schema"
import { RecipientService } from "@/services/recipient.service"
type FieldErrors = Record<string, string[]>
type RecipientUseCaseResult =
| {
success: true
}
| {
success: false
errors: FieldErrors
}
function recipientError(errors: FieldErrors): RecipientUseCaseResult {
return {
success: false,
errors,
}
}
function uniqueErrorFor(error: unknown): FieldErrors | null {
if (
!(error instanceof Prisma.PrismaClientKnownRequestError) ||
error.code !== "P2002"
) {
return null
}
const target = Array.isArray(error.meta?.target) ? error.meta.target : []
if (target.includes("username")) {
return { username: ["Username already exists"] }
}
if (target.includes("email")) {
return { email: ["Email already exists"] }
}
return { username: ["Username already exists"] }
}
export async function createRecipientUseCase(
input: CreateRecipientFormType,
): Promise<RecipientUseCaseResult> {
const { username, firstName, lastName, department, email, phone } = input
try {
return await prisma.$transaction(async (tx) => {
const existingRecipientUsername = await RecipientService.findByUsername(
username,
tx,
)
if (existingRecipientUsername) {
return recipientError({ username: ["Username already exists"] })
}
if (email) {
const existingRecipientEmail = await RecipientService.findByEmail(
email,
tx,
)
if (existingRecipientEmail) {
return recipientError({ email: ["Email already exists"] })
}
}
await RecipientService.create(
{
username: username || (firstName[0] + lastName).toLowerCase(),
firstName,
lastName,
department,
email: email || null,
phone: phone || null,
},
tx,
)
return {
success: true,
}
})
} catch (error) {
const errors = uniqueErrorFor(error)
if (errors) {
return recipientError(errors)
}
throw error
}
}
export async function updateRecipientUseCase(
input: UpdateRecipientFormType,
): Promise<RecipientUseCaseResult> {
const { id, username, firstName, lastName, department, email, phone } = input
try {
return await prisma.$transaction(async (tx) => {
const existingRecipient = await RecipientService.findByUsername(
username,
tx,
)
if (existingRecipient && existingRecipient.id !== id) {
return recipientError({ username: ["Username already exists"] })
}
if (email) {
const existingRecipientEmail = await RecipientService.findByEmail(
email,
tx,
)
if (existingRecipientEmail && existingRecipientEmail.id !== id) {
return recipientError({ email: ["Email already exists"] })
}
}
await RecipientService.update(
id,
{
username,
firstName,
lastName,
department,
email: email || null,
phone: phone || null,
},
tx,
)
return {
success: true,
}
})
} catch (error) {
const errors = uniqueErrorFor(error)
if (errors) {
return recipientError(errors)
}
throw error
}
}