import { prisma } from "@/lib/prisma"; import { Prisma } from "@/app/generated/prisma/client"; import type { Vibe } from "@/app/generated/prisma/enums"; export interface PeopleFilters { city?: string; interest?: string; vibe?: Vibe; } export const userRepo = { async findByEmail(email: string) { return prisma.user.findUnique({ where: { email } }); }, async findById(id: string) { return prisma.user.findUnique({ where: { id } }); }, /** Profil publik (tanpa password) untuk halaman akun. */ async findPublicProfileById(id: string) { return prisma.user.findUnique({ where: { id }, select: { id: true, name: true, email: true, image: true, createdAt: true, }, }); }, /** * Profil sosial publik untuk halaman /u/[id]. JANGAN sertakan field sensitif * (email, password, KYC). Hanya yang user pilih untuk dibagikan. */ async findSocialProfileById(id: string) { return prisma.user.findUnique({ where: { id }, select: { id: true, name: true, image: true, createdAt: true, profile: { select: { bio: true, city: true, interests: true, instagram: true, vibe: true, }, }, organizerVerification: { select: { status: true } }, }, }); }, /** * Discovery /people: ambil user yang punya profil sosial terisi (minimal salah * satu dari bio/city/interests/vibe). Filter optional by city/interest/vibe. * Tidak ekspos email/KYC. */ async findPeople(filters?: PeopleFilters, limit = 60) { const profileWhere: Prisma.UserProfileWhereInput = { OR: [ { bio: { not: null } }, { city: { not: null } }, { vibe: { not: null } }, { interests: { isEmpty: false } }, ], }; if (filters?.city) { profileWhere.city = { contains: filters.city, mode: "insensitive", }; } if (filters?.interest) { profileWhere.interests = { has: filters.interest.toLowerCase() }; } if (filters?.vibe) { profileWhere.vibe = filters.vibe; } return prisma.user.findMany({ where: { profile: { is: profileWhere } }, select: { id: true, name: true, image: true, createdAt: true, profile: { select: { bio: true, city: true, interests: true, vibe: true, }, }, organizerVerification: { select: { status: true } }, }, orderBy: { createdAt: "desc" }, take: limit, }); }, async create(data: Prisma.UserCreateInput) { 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 { 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). */ async markAcceptedTerms(id: string) { const result = await prisma.user.updateMany({ where: { id, acceptedTermsAndPrivacy: false }, data: { acceptedTermsAndPrivacy: true, acceptedAt: new Date() }, }); return { updated: result.count > 0 }; }, };