2026-03-10 08:17:45 -04:00
|
|
|
import { NextRequest, NextResponse } from "next/server";
|
|
|
|
|
import { prisma } from "@/lib/db";
|
2026-04-07 13:38:08 -04:00
|
|
|
import { deleteObject } from "@/lib/minio";
|
2026-04-07 13:23:29 -04:00
|
|
|
import { fireIntegrationEvent } from "@/lib/integrations";
|
|
|
|
|
import { logActivity, diffCardFields } from "@/lib/activity-log";
|
2026-03-10 08:17:45 -04:00
|
|
|
|
|
|
|
|
export async function GET(
|
|
|
|
|
_request: NextRequest,
|
|
|
|
|
{ params }: { params: Promise<{ id: string }> }
|
|
|
|
|
) {
|
|
|
|
|
try {
|
|
|
|
|
const { id } = await params;
|
|
|
|
|
const card = await prisma.responseCard.findUnique({
|
|
|
|
|
where: { id },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!card) {
|
|
|
|
|
return NextResponse.json({ error: "Card not found" }, { status: 404 });
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 13:38:08 -04:00
|
|
|
const frontImageUrl = card.frontImagePath
|
|
|
|
|
? `/api/images/${card.frontImagePath}`
|
|
|
|
|
: null;
|
|
|
|
|
const backImageUrl = card.backImagePath
|
|
|
|
|
? `/api/images/${card.backImagePath}`
|
|
|
|
|
: null;
|
2026-03-10 08:17:45 -04:00
|
|
|
|
|
|
|
|
return NextResponse.json({
|
|
|
|
|
...card,
|
|
|
|
|
frontImageUrl,
|
|
|
|
|
backImageUrl,
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("[cards/[id] GET]", error);
|
|
|
|
|
return NextResponse.json(
|
|
|
|
|
{ error: "Failed to fetch card" },
|
|
|
|
|
{ status: 500 }
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function PUT(
|
|
|
|
|
request: NextRequest,
|
|
|
|
|
{ params }: { params: Promise<{ id: string }> }
|
|
|
|
|
) {
|
|
|
|
|
try {
|
|
|
|
|
const { id } = await params;
|
|
|
|
|
const card = await prisma.responseCard.findUnique({
|
|
|
|
|
where: { id },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!card) {
|
|
|
|
|
return NextResponse.json({ error: "Card not found" }, { status: 404 });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const body = await request.json().catch(() => ({}));
|
|
|
|
|
const data: Record<string, unknown> = {};
|
|
|
|
|
|
|
|
|
|
const stringFields = [
|
|
|
|
|
"name",
|
|
|
|
|
"gender",
|
|
|
|
|
"dateOfBirth",
|
|
|
|
|
"maritalStatus",
|
|
|
|
|
"maritalStatusOther",
|
|
|
|
|
"visitType",
|
|
|
|
|
"cellPhone",
|
|
|
|
|
"homePhone",
|
|
|
|
|
"email",
|
|
|
|
|
"address",
|
|
|
|
|
"aptNumber",
|
|
|
|
|
"city",
|
|
|
|
|
"state",
|
|
|
|
|
"zip",
|
|
|
|
|
"prayerRequests",
|
|
|
|
|
"messageTopicsOther",
|
|
|
|
|
"attendanceDuration",
|
|
|
|
|
"campusPreferenceOther",
|
|
|
|
|
"howHeardOther",
|
|
|
|
|
"serviceAttended",
|
|
|
|
|
"ocrStatus",
|
|
|
|
|
"reviewStatus",
|
|
|
|
|
"ocrError",
|
|
|
|
|
];
|
|
|
|
|
for (const field of stringFields) {
|
|
|
|
|
if (body[field] != null) data[field] = String(body[field]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (body.prayerForTeam != null) data.prayerForTeam = Boolean(body.prayerForTeam);
|
|
|
|
|
if (body.prayerConfidential != null) data.prayerConfidential = Boolean(body.prayerConfidential);
|
|
|
|
|
if (body.ocrConfidence != null) data.ocrConfidence = Number(body.ocrConfidence);
|
|
|
|
|
if (body.messageTopics != null) data.messageTopics = body.messageTopics;
|
|
|
|
|
if (body.nextStep != null) data.nextStep = body.nextStep;
|
|
|
|
|
if (body.campusPreference != null) data.campusPreference = body.campusPreference;
|
|
|
|
|
if (body.howHeard != null) data.howHeard = body.howHeard;
|
|
|
|
|
if (body.rawOcrResponse != null) data.rawOcrResponse = body.rawOcrResponse;
|
|
|
|
|
|
2026-04-07 13:23:29 -04:00
|
|
|
const oldCard = card as unknown as Record<string, unknown>;
|
|
|
|
|
|
2026-03-10 08:17:45 -04:00
|
|
|
const updated = await prisma.responseCard.update({
|
|
|
|
|
where: { id },
|
|
|
|
|
data: data as Parameters<typeof prisma.responseCard.update>[0]["data"],
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-07 13:23:29 -04:00
|
|
|
const newCard = updated as unknown as Record<string, unknown>;
|
|
|
|
|
const changes = diffCardFields(oldCard, newCard);
|
|
|
|
|
if (changes.length > 0) {
|
|
|
|
|
logActivity(id, "manual_edit", "user", `${changes.length} field(s) updated manually`, changes).catch(() => {});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const oldStatus = card.reviewStatus;
|
|
|
|
|
const newStatus = updated.reviewStatus;
|
|
|
|
|
if (oldStatus !== newStatus) {
|
|
|
|
|
if (newStatus === "reviewed") {
|
|
|
|
|
fireIntegrationEvent("card_reviewed", id, { oldCard }).catch(() => {});
|
|
|
|
|
} else if (newStatus === "exported") {
|
|
|
|
|
fireIntegrationEvent("card_exported", id, { oldCard }).catch(() => {});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-10 08:17:45 -04:00
|
|
|
return NextResponse.json(updated);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("[cards/[id] PUT]", error);
|
|
|
|
|
return NextResponse.json(
|
|
|
|
|
{ error: "Failed to update card" },
|
|
|
|
|
{ status: 500 }
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function DELETE(
|
|
|
|
|
_request: NextRequest,
|
|
|
|
|
{ params }: { params: Promise<{ id: string }> }
|
|
|
|
|
) {
|
|
|
|
|
try {
|
|
|
|
|
const { id } = await params;
|
|
|
|
|
const card = await prisma.responseCard.findUnique({
|
|
|
|
|
where: { id },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!card) {
|
|
|
|
|
return NextResponse.json({ error: "Card not found" }, { status: 404 });
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 13:23:29 -04:00
|
|
|
fireIntegrationEvent("card_deleted", id).catch(() => {});
|
|
|
|
|
|
2026-03-10 08:17:45 -04:00
|
|
|
const deletePromises: Promise<void>[] = [];
|
|
|
|
|
if (card.frontImagePath) deletePromises.push(deleteObject(card.frontImagePath));
|
|
|
|
|
if (card.backImagePath) deletePromises.push(deleteObject(card.backImagePath));
|
|
|
|
|
await Promise.allSettled(deletePromises);
|
|
|
|
|
|
|
|
|
|
await prisma.responseCard.delete({
|
|
|
|
|
where: { id },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return NextResponse.json({ success: true });
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("[cards/[id] DELETE]", error);
|
|
|
|
|
return NextResponse.json(
|
|
|
|
|
{ error: "Failed to delete card" },
|
|
|
|
|
{ status: 500 }
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|