admin roadmap filter & search, user management, reopen rejected, system health
This commit is contained in:
@@ -111,6 +111,135 @@ export const userRepo = {
|
||||
return prisma.user.create({ data });
|
||||
},
|
||||
|
||||
/**
|
||||
* Admin search: by email/name (case-insensitive contains) + filter
|
||||
* suspended status. Limit 100 supaya tidak load semua user di list page.
|
||||
*/
|
||||
async searchForAdmin(filters: { q?: string; suspended?: boolean }) {
|
||||
const where: Prisma.UserWhereInput = {};
|
||||
if (filters.q) {
|
||||
where.OR = [
|
||||
{ email: { contains: filters.q, mode: "insensitive" } },
|
||||
{ name: { contains: filters.q, mode: "insensitive" } },
|
||||
];
|
||||
}
|
||||
if (typeof filters.suspended === "boolean") {
|
||||
where.suspended = filters.suspended;
|
||||
}
|
||||
return prisma.user.findMany({
|
||||
where,
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
image: true,
|
||||
createdAt: true,
|
||||
suspended: true,
|
||||
suspendedAt: true,
|
||||
organizerVerification: { select: { status: true } },
|
||||
_count: {
|
||||
select: {
|
||||
trips: true,
|
||||
participations: { where: { status: { not: "CANCELLED" } } },
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: "desc" },
|
||||
take: 100,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Detail user untuk admin: full profile + booking history + organized
|
||||
* trips + verification. Tidak ekspos password atau OAuth token.
|
||||
*/
|
||||
async findByIdForAdmin(id: string) {
|
||||
return prisma.user.findUnique({
|
||||
where: { id },
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
image: true,
|
||||
createdAt: true,
|
||||
acceptedTermsAndPrivacy: true,
|
||||
acceptedAt: true,
|
||||
suspended: true,
|
||||
suspendedAt: true,
|
||||
suspendedReason: true,
|
||||
suspendedBy: { select: { id: true, name: true, email: true } },
|
||||
profile: true,
|
||||
organizerVerification: {
|
||||
select: {
|
||||
id: true,
|
||||
status: true,
|
||||
createdAt: true,
|
||||
reviewedAt: true,
|
||||
rejectionReason: true,
|
||||
},
|
||||
},
|
||||
trips: {
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
destination: true,
|
||||
date: true,
|
||||
status: true,
|
||||
price: true,
|
||||
_count: {
|
||||
select: {
|
||||
participants: { where: { status: { not: "CANCELLED" } } },
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { date: "desc" },
|
||||
take: 50,
|
||||
},
|
||||
bookings: {
|
||||
select: {
|
||||
id: true,
|
||||
amount: true,
|
||||
status: true,
|
||||
createdAt: true,
|
||||
trip: {
|
||||
select: { id: true, title: true, date: true, organizerId: true },
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: "desc" },
|
||||
take: 50,
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
/** Cek cepat status suspended — dipakai auth guard di server actions. */
|
||||
async isSuspended(id: string): Promise<boolean> {
|
||||
const u = await prisma.user.findUnique({
|
||||
where: { id },
|
||||
select: { suspended: true },
|
||||
});
|
||||
return u?.suspended === true;
|
||||
},
|
||||
|
||||
async setSuspension(
|
||||
id: string,
|
||||
data: {
|
||||
suspended: boolean;
|
||||
suspendedById?: string | null;
|
||||
suspendedReason?: string | null;
|
||||
}
|
||||
) {
|
||||
return prisma.user.update({
|
||||
where: { id },
|
||||
data: {
|
||||
suspended: data.suspended,
|
||||
suspendedAt: data.suspended ? new Date() : null,
|
||||
suspendedById: data.suspended ? data.suspendedById ?? null : null,
|
||||
suspendedReason: data.suspended ? data.suspendedReason ?? null : null,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Tandai user sudah accept T&C/Privacy. Idempotent: kalau sudah `true`,
|
||||
* tidak overwrite `acceptedAt` (audit trail pertama tetap akurat).
|
||||
|
||||
Reference in New Issue
Block a user