import "dotenv/config"; import { PrismaClient } from "../app/generated/prisma/client"; import type { Prisma } from "../app/generated/prisma/client"; import type { ActivityCategory, Vibe, } from "../app/generated/prisma/enums"; import { PrismaPg } from "@prisma/adapter-pg"; import bcrypt from "bcryptjs"; import { encryptString, hmacHex } from "../lib/crypto"; const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL!, }); const prisma = new PrismaClient({ adapter }); // ============================================================================ // Helpers // ============================================================================ const utc = (y: number, m0: number, d: number, h = 12, min = 0) => new Date(Date.UTC(y, m0, d, h, min, 0, 0)); const img = (id: string) => `https://images.unsplash.com/photo-${id}?w=1200&q=80&auto=format&fit=crop`; const SEED_PASSWORD = "password123"; // ============================================================================ // Itineraries — array terstruktur per hari/jam. Setiap item: {day, startTime, // endTime, activity}. Disimpan ke TripItineraryItem (lihat schema.prisma). // ============================================================================ interface SeedItineraryItem { day: number; startTime: string; endTime?: string; activity: string; } const ITIN_PAPANDAYAN: SeedItineraryItem[] = [ { day: 1, startTime: "05:00", endTime: "05:30", activity: "Meeting & briefing di Alun-alun Garut" }, { day: 1, startTime: "05:30", endTime: "07:00", activity: "Perjalanan menuju basecamp Cisurupan" }, { day: 1, startTime: "07:00", endTime: "08:00", activity: "Sarapan + repacking + pemanasan" }, { day: 1, startTime: "08:00", endTime: "11:00", activity: "Trekking Camp David — Hutan Mati — Pondok Salada" }, { day: 1, startTime: "11:00", endTime: "12:30", activity: "Setup tenda di Pondok Salada" }, { day: 1, startTime: "12:30", endTime: "14:00", activity: "ISHOMA + games kenalan grup" }, { day: 1, startTime: "14:00", endTime: "17:00", activity: "Eksplor Tegal Alun & Hutan Mati (golden hour foto)" }, { day: 1, startTime: "17:00", endTime: "19:00", activity: "Masak bareng + makan malam" }, { day: 1, startTime: "19:00", endTime: "21:00", activity: "Api unggun, kopi, sharing rencana summit" }, { day: 1, startTime: "21:00", activity: "Istirahat" }, { day: 2, startTime: "03:30", endTime: "04:00", activity: "Bangun + sarapan ringan + air panas" }, { day: 2, startTime: "04:00", endTime: "05:30", activity: "Summit attack ke puncak Papandayan" }, { day: 2, startTime: "05:30", endTime: "07:00", activity: "Sunrise + foto bareng di puncak" }, { day: 2, startTime: "07:00", endTime: "09:00", activity: "Turun ke camp + sarapan utama" }, { day: 2, startTime: "09:00", endTime: "11:00", activity: "Beres-beres tenda + repacking" }, { day: 2, startTime: "11:00", endTime: "13:30", activity: "Turun ke basecamp Cisurupan" }, { day: 2, startTime: "13:30", endTime: "14:30", activity: "Bersih-bersih + makan siang" }, { day: 2, startTime: "14:30", endTime: "16:30", activity: "Perjalanan kembali ke Garut" }, { day: 2, startTime: "16:30", activity: "Sampai Garut, bubar grup" }, ]; const ITIN_CIREMAI: SeedItineraryItem[] = [ { day: 1, startTime: "04:00", endTime: "04:30", activity: "Meeting & briefing di Stasiun Kuningan" }, { day: 1, startTime: "04:30", endTime: "06:30", activity: "Perjalanan ke basecamp Apuy via Maja & Argapura" }, { day: 1, startTime: "06:30", endTime: "07:30", activity: "Sarapan + registrasi simaksi + repacking" }, { day: 1, startTime: "07:30", endTime: "10:30", activity: "Trek Pos 1 (Berod) → Pos 2 (Arban) → Pos 3 (Tegal Masawa)" }, { day: 1, startTime: "10:30", endTime: "11:30", activity: "ISHOMA di Pos 4 (Tegal Jamuju)" }, { day: 1, startTime: "11:30", endTime: "14:30", activity: "Lanjut Pos 5 (Sanghyang Rangkah) → Pos 6 (Goa Walet)" }, { day: 1, startTime: "14:30", endTime: "16:00", activity: "Setup tenda di Pos 6" }, { day: 1, startTime: "16:00", endTime: "18:00", activity: "Acara bebas + makan sore + persiapan summit" }, { day: 1, startTime: "18:00", endTime: "20:00", activity: "Briefing summit + early dinner" }, { day: 1, startTime: "20:00", activity: "Istirahat (bangun dini hari)" }, { day: 2, startTime: "02:00", endTime: "02:30", activity: "Bangun + cemilan + minuman hangat" }, { day: 2, startTime: "02:30", endTime: "05:00", activity: "Summit attack ke puncak Sunan Cirebon (3.078 mdpl)" }, { day: 2, startTime: "05:00", endTime: "06:30", activity: "Sunrise di puncak Ciremai" }, { day: 2, startTime: "06:30", endTime: "08:30", activity: "Turun ke Pos 6 + sarapan" }, { day: 2, startTime: "08:30", endTime: "10:30", activity: "Beres tenda + repacking" }, { day: 2, startTime: "10:30", endTime: "14:00", activity: "Turun ke basecamp Apuy (track curam — hati-hati lutut)" }, { day: 2, startTime: "14:00", endTime: "15:00", activity: "Bersih-bersih + makan siang di basecamp" }, { day: 2, startTime: "15:00", endTime: "17:00", activity: "Kembali ke Stasiun Kuningan" }, { day: 2, startTime: "17:00", activity: "Bubar grup" }, ]; const ITIN_CAMPING: SeedItineraryItem[] = [ { day: 1, startTime: "13:00", endTime: "13:30", activity: "Meeting di Pertigaan Pasar Lembang" }, { day: 1, startTime: "13:30", endTime: "15:00", activity: "Perjalanan ke Ranca Upas via Ciwidey" }, { day: 1, startTime: "15:00", endTime: "16:00", activity: "Check-in + setup tenda dome (sudah disiapkan tim)" }, { day: 1, startTime: "16:00", endTime: "17:30", activity: "Tour camp area + ketemu rusa-rusa" }, { day: 1, startTime: "17:30", endTime: "19:00", activity: "Persiapan BBQ + nyalakan api unggun" }, { day: 1, startTime: "19:00", endTime: "21:00", activity: "Makan malam BBQ" }, { day: 1, startTime: "21:00", endTime: "23:00", activity: "Live music akustik + games" }, { day: 1, startTime: "23:00", activity: "Istirahat" }, { day: 2, startTime: "06:00", endTime: "07:00", activity: "Sunrise + foto di hutan pinus" }, { day: 2, startTime: "07:00", endTime: "08:30", activity: "Sarapan (nasi goreng + roti bakar + kopi)" }, { day: 2, startTime: "08:30", endTime: "10:00", activity: "Memberi makan rusa + sesi foto" }, { day: 2, startTime: "10:00", endTime: "11:00", activity: "Beres tenda + bersih-bersih" }, { day: 2, startTime: "11:00", endTime: "11:30", activity: "Pulang menuju Lembang" }, { day: 2, startTime: "12:00", activity: "Sampai Lembang, bubar grup" }, ]; const ITIN_PAHAWANG: SeedItineraryItem[] = [ { day: 1, startTime: "07:00", endTime: "07:30", activity: "Meeting di Dermaga Ketapang, Lampung Selatan" }, { day: 1, startTime: "07:30", endTime: "08:30", activity: "Briefing safety + fitting alat snorkel" }, { day: 1, startTime: "08:30", endTime: "09:30", activity: "Sailing menuju Pulau Pahawang Kecil" }, { day: 1, startTime: "09:30", endTime: "11:30", activity: "Snorkeling spot Cukuh Bedil — terumbu warna-warni" }, { day: 1, startTime: "11:30", endTime: "12:30", activity: "Pindah spot ke Pulau Kelagian Kecil" }, { day: 1, startTime: "12:30", endTime: "14:00", activity: "Makan siang + istirahat di pasir putih" }, { day: 1, startTime: "14:00", endTime: "15:30", activity: "Snorkeling Tanjung Putus + sesi foto underwater" }, { day: 1, startTime: "15:30", endTime: "16:30", activity: "Sailing kembali ke dermaga" }, { day: 1, startTime: "16:30", endTime: "17:00", activity: "Bersih-bersih + bubar grup" }, ]; const ITIN_DIVING: SeedItineraryItem[] = [ { day: 1, startTime: "06:30", endTime: "07:00", activity: "Meeting di dive shop Tulamben + welcome coffee" }, { day: 1, startTime: "07:00", endTime: "08:00", activity: "Briefing dive plan + cek sertifikasi + fitting gear" }, { day: 1, startTime: "08:00", endTime: "09:00", activity: "Surface interval + pengecekan tank/regulator" }, { day: 1, startTime: "09:00", endTime: "10:30", activity: "Dive #1 — USS Liberty Wreck (5–28m, ~50 menit bottom time)" }, { day: 1, startTime: "10:30", endTime: "12:00", activity: "Surface interval + brunch + log dive" }, { day: 1, startTime: "12:00", endTime: "13:30", activity: "Dive #2 — Coral Garden / Drop Off (~50 menit)" }, { day: 1, startTime: "13:30", endTime: "15:00", activity: "Debrief + makan siang" }, { day: 1, startTime: "15:00", endTime: "17:00", activity: "Acara bebas (rest atau eksplor desa Tulamben)" }, { day: 1, startTime: "17:00", endTime: "19:00", activity: "Sunset di pantai Tulamben + dinner" }, { day: 1, startTime: "19:00", activity: "Istirahat di homestay (mandiri)" }, { day: 2, startTime: "06:00", endTime: "06:30", activity: "Bangun + kopi" }, { day: 2, startTime: "06:30", endTime: "07:30", activity: "Briefing dive #3 (early morning visibility tinggi)" }, { day: 2, startTime: "07:30", endTime: "09:00", activity: "Dive #3 — Liberty Wreck pagi" }, { day: 2, startTime: "09:00", endTime: "10:30", activity: "Surface interval + sarapan + log" }, { day: 2, startTime: "10:30", endTime: "12:00", activity: "Dive #4 (opsional, fun dive shallow reef)" }, { day: 2, startTime: "12:00", endTime: "13:00", activity: "Bersih gear + debrief akhir" }, { day: 2, startTime: "13:00", endTime: "14:00", activity: "Makan siang penutupan" }, { day: 2, startTime: "14:00", activity: "Bubar grup" }, ]; const ITIN_ISLANDHOP: SeedItineraryItem[] = [ { day: 1, startTime: "07:00", endTime: "07:30", activity: "Meeting di Pelabuhan Kartini Jepara" }, { day: 1, startTime: "07:30", endTime: "13:00", activity: "Penyeberangan kapal feri Jepara → Karimun Jawa" }, { day: 1, startTime: "13:00", endTime: "14:00", activity: "Tiba di Pelabuhan Karimun + transfer homestay" }, { day: 1, startTime: "14:00", endTime: "15:00", activity: "Check-in homestay + ISHOMA" }, { day: 1, startTime: "15:00", endTime: "17:30", activity: "Sunset di Bukit Love + foto-foto" }, { day: 1, startTime: "17:30", endTime: "19:00", activity: "Bersih-bersih + makan malam" }, { day: 1, startTime: "19:00", endTime: "21:00", activity: "Alun-alun Karimun + jajan kuliner" }, { day: 1, startTime: "21:00", activity: "Istirahat" }, { day: 2, startTime: "06:30", endTime: "07:30", activity: "Sarapan + briefing hopping" }, { day: 2, startTime: "07:30", endTime: "09:30", activity: "Boat ke Pulau Menjangan Kecil — snorkeling spot terumbu" }, { day: 2, startTime: "09:30", endTime: "11:30", activity: "Pulau Menjangan Besar — interaksi hiu (penangkaran)" }, { day: 2, startTime: "11:30", endTime: "13:00", activity: "Makan siang BBQ ikan di Pulau Cemara Besar" }, { day: 2, startTime: "13:00", endTime: "15:00", activity: "Pulau Cemara Kecil + foto pasir putih" }, { day: 2, startTime: "15:00", endTime: "17:00", activity: "Pulau Cilik — sunset + snorkel terakhir" }, { day: 2, startTime: "17:00", endTime: "18:30", activity: "Kembali ke homestay + bersih-bersih" }, { day: 2, startTime: "18:30", endTime: "20:00", activity: "Makan malam seafood" }, { day: 2, startTime: "20:00", activity: "Acara bebas" }, { day: 3, startTime: "06:00", endTime: "07:00", activity: "Sunrise di Tanjung Gelam" }, { day: 3, startTime: "07:00", endTime: "09:00", activity: "Sarapan + pack-up" }, { day: 3, startTime: "09:00", endTime: "10:00", activity: "Belanja oleh-oleh di pelabuhan" }, { day: 3, startTime: "10:00", endTime: "16:00", activity: "Penyeberangan kapal feri Karimun → Jepara" }, { day: 3, startTime: "16:00", endTime: "17:00", activity: "Tiba di Pelabuhan Kartini, bubar grup" }, ]; const ITIN_CITYTRIP: SeedItineraryItem[] = [ { day: 1, startTime: "08:00", endTime: "08:30", activity: "Meeting di Stasiun Tugu Yogyakarta" }, { day: 1, startTime: "08:30", endTime: "10:00", activity: "Sarapan Gudeg Yu Djum + briefing rute" }, { day: 1, startTime: "10:00", endTime: "12:00", activity: "Kotagede — kerajinan perak + Masjid Mataram" }, { day: 1, startTime: "12:00", endTime: "13:30", activity: "Makan siang Sate Klathak Pak Pong" }, { day: 1, startTime: "13:30", endTime: "16:00", activity: "Tamansari — pemandian Sultan + sumur Gumuling" }, { day: 1, startTime: "16:00", endTime: "17:30", activity: "Coffee break di kedai lokal Prawirotaman" }, { day: 1, startTime: "17:30", endTime: "19:30", activity: "Sunset di Bukit Bintang (Jl. Imogiri)" }, { day: 1, startTime: "19:30", endTime: "21:30", activity: "Angkringan Lik Man — kopi joss + nasi kucing" }, { day: 1, startTime: "21:30", activity: "Drop ke penginapan masing-masing" }, { day: 2, startTime: "06:00", endTime: "07:00", activity: "Pickup dari penginapan" }, { day: 2, startTime: "07:00", endTime: "09:30", activity: "Perjalanan ke Kalibiru, Kulon Progo" }, { day: 2, startTime: "09:30", endTime: "11:30", activity: "Kalibiru — spot foto rumah pohon di tebing" }, { day: 2, startTime: "11:30", endTime: "13:00", activity: "Makan siang pecel di warung lokal" }, { day: 2, startTime: "13:00", endTime: "15:00", activity: "Pinus Pengger — instalasi seni alam" }, { day: 2, startTime: "15:00", endTime: "16:30", activity: "Heha Sky View (opsional, cek cuaca)" }, { day: 2, startTime: "16:30", endTime: "18:00", activity: "Kembali ke kota — drop di Stasiun Tugu / Bandara" }, { day: 2, startTime: "18:00", activity: "Bubar grup" }, ]; const ITIN_CULINARY: SeedItineraryItem[] = [ { day: 1, startTime: "09:00", endTime: "09:30", activity: "Meeting di Stasiun Bandung pintu utara + briefing rute" }, { day: 1, startTime: "09:30", endTime: "10:15", activity: "Stop 1: Surabi Enhaii (sarapan tradisional)" }, { day: 1, startTime: "10:15", endTime: "11:00", activity: "Stop 2: Lotek Kalipah Apo" }, { day: 1, startTime: "11:00", endTime: "11:45", activity: "Stop 3: Mie Kocok Mang Dadeng (Kebon Jukut)" }, { day: 1, startTime: "11:45", endTime: "12:30", activity: "Stop 4: Bakso Akung (cabang Burangrang)" }, { day: 1, startTime: "12:30", endTime: "13:30", activity: "Istirahat + jalan santai di Cihampelas" }, { day: 1, startTime: "13:30", endTime: "14:15", activity: "Stop 5: Batagor Kingsley" }, { day: 1, startTime: "14:15", endTime: "15:00", activity: "Stop 6: Cuanki Serayu" }, { day: 1, startTime: "15:00", endTime: "15:45", activity: "Stop 7: Es Cendol Elizabeth" }, { day: 1, startTime: "15:45", endTime: "16:30", activity: "Stop 8: Roti Gempol & Kopi Anjis (penutup)" }, { day: 1, startTime: "16:30", endTime: "17:00", activity: "Closing + foto bareng di Braga" }, ]; const ITIN_CONCERT: SeedItineraryItem[] = [ { day: 1, startTime: "17:00", endTime: "17:30", activity: "Meeting di Plaza GBK, depan loket Cat 1" }, { day: 1, startTime: "17:30", endTime: "18:30", activity: "Foto bareng pre-show + obrolan singkat" }, { day: 1, startTime: "18:30", endTime: "19:00", activity: "Masuk venue bareng (kategori tetap masing-masing)" }, { day: 1, startTime: "19:00", endTime: "22:30", activity: "Konser Coldplay — Music of the Spheres" }, { day: 1, startTime: "22:30", endTime: "23:00", activity: "Berkumpul lagi di luar gerbang utama GBK" }, { day: 1, startTime: "23:00", activity: "After-party dinner di Senayan (resto TBA via grup WA)" }, ]; const ITIN_WORKSHOP: SeedItineraryItem[] = [ { day: 1, startTime: "04:00", endTime: "04:30", activity: "Meeting di Alun-alun Pangalengan" }, { day: 1, startTime: "04:30", endTime: "05:30", activity: "Briefing teknis + setup peralatan" }, { day: 1, startTime: "05:30", endTime: "07:30", activity: "Sunrise shoot di Perkebunan Teh Malabar" }, { day: 1, startTime: "07:30", endTime: "09:00", activity: "Sarapan + sesi review foto bareng mentor" }, { day: 1, startTime: "09:00", endTime: "11:00", activity: "Materi indoor: long exposure & filter ND" }, { day: 1, startTime: "11:00", endTime: "13:00", activity: "ISHOMA + transfer ke Situ Cileunca" }, { day: 1, startTime: "13:00", endTime: "16:00", activity: "Workshop on-field di Situ Cileunca (panorama, refleksi)" }, { day: 1, startTime: "16:00", endTime: "18:00", activity: "Golden hour shoot di Bukit Nini" }, { day: 1, startTime: "18:00", endTime: "19:30", activity: "Makan malam di villa" }, { day: 1, startTime: "19:30", endTime: "22:00", activity: "Sesi malam — milky way / star trail (cuaca permitting)" }, { day: 1, startTime: "22:00", activity: "Istirahat di villa" }, { day: 2, startTime: "05:00", endTime: "07:00", activity: "Sunrise shoot di Pangalengan + foto siluet" }, { day: 2, startTime: "07:00", endTime: "08:30", activity: "Sarapan + diskusi hasil" }, { day: 2, startTime: "08:30", endTime: "11:00", activity: "Sesi editing Lightroom (laptop pribadi)" }, { day: 2, startTime: "11:00", endTime: "12:00", activity: "Review akhir + sertifikat" }, { day: 2, startTime: "12:00", endTime: "13:00", activity: "Makan siang penutupan" }, { day: 2, startTime: "13:00", endTime: "15:00", activity: "Kembali ke Alun-alun Pangalengan" }, { day: 2, startTime: "15:00", activity: "Bubar grup" }, ]; const ITIN_RETREAT: SeedItineraryItem[] = [ { day: 1, startTime: "14:00", endTime: "15:00", activity: "Check-in Villa Sawah Ubud + welcome drink (jamu)" }, { day: 1, startTime: "15:00", endTime: "16:00", activity: "Tour fasilitas + pembagian welcome kit" }, { day: 1, startTime: "16:00", endTime: "17:30", activity: "Yin Yoga pembuka — release perjalanan" }, { day: 1, startTime: "17:30", endTime: "18:30", activity: "Journaling: niat & ekspektasi retreat" }, { day: 1, startTime: "18:30", endTime: "20:00", activity: "Dinner vegan (set menu)" }, { day: 1, startTime: "20:00", endTime: "21:00", activity: "Circle pengenalan + meditasi singkat" }, { day: 1, startTime: "21:00", activity: "Lights-out" }, { day: 2, startTime: "06:00", endTime: "07:30", activity: "Hatha Yoga matahari terbit" }, { day: 2, startTime: "07:30", endTime: "09:00", activity: "Sarapan vegan + tea ceremony" }, { day: 2, startTime: "09:00", endTime: "10:30", activity: "Meditasi guided: body scan" }, { day: 2, startTime: "10:30", endTime: "12:00", activity: "Pranayama (latihan napas)" }, { day: 2, startTime: "12:00", endTime: "13:30", activity: "Lunch + acara bebas (sawah walk)" }, { day: 2, startTime: "13:30", endTime: "15:00", activity: "Sound healing dengan singing bowl" }, { day: 2, startTime: "15:00", endTime: "16:30", activity: "Workshop: mindful eating + jamu making" }, { day: 2, startTime: "16:30", endTime: "18:00", activity: "Yin Yoga sore + savasana panjang" }, { day: 2, startTime: "18:00", endTime: "19:30", activity: "Dinner vegan" }, { day: 2, startTime: "19:30", endTime: "21:00", activity: "Sharing circle + meditasi malam" }, { day: 2, startTime: "21:00", activity: "Lights-out" }, { day: 3, startTime: "06:00", endTime: "07:30", activity: "Vinyasa Flow penutupan" }, { day: 3, startTime: "07:30", endTime: "09:00", activity: "Sarapan + closing journaling" }, { day: 3, startTime: "09:00", endTime: "10:30", activity: "Closing circle + tukar pesan" }, { day: 3, startTime: "10:30", endTime: "11:30", activity: "Check-out + pelukan perpisahan" }, { day: 3, startTime: "11:30", endTime: "14:00", activity: "Acara bebas (rekomendasi spa/cafe sekitar)" }, { day: 3, startTime: "14:00", activity: "Trip resmi ditutup" }, ]; // ============================================================================ // 1. Cleanup — urutan FK aware (Payment → Booking → Review → Image → // Participant → Trip → OrgVerification → Profile → Account → User). // Seed wajib idempotent supaya `npm run seed` bisa di-run berulang. // ============================================================================ async function cleanup() { // Refund & Payout pakai onDelete: Restrict ke Booking/Payment, jadi mereka // wajib dihapus duluan sebelum Booking/Payment bisa dibersihkan. await prisma.refund.deleteMany(); await prisma.payout.deleteMany(); await prisma.payment.deleteMany(); await prisma.booking.deleteMany(); await prisma.tripReview.deleteMany(); await prisma.tripImage.deleteMany(); await prisma.tripItineraryItem.deleteMany(); await prisma.tripParticipant.deleteMany(); await prisma.trip.deleteMany(); await prisma.organizerVerification.deleteMany(); await prisma.userProfile.deleteMany(); await prisma.account.deleteMany(); await prisma.user.deleteMany(); } // ============================================================================ // 2. Users + Profile // ============================================================================ interface SeedUser { key: string; name: string; email: string; isAdmin?: boolean; profile?: { bio?: string; city?: string; interests?: string[]; instagram?: string; vibe?: Vibe; }; } const SEED_USERS: SeedUser[] = [ // Admin { key: "admin", name: "Admin Setrip", email: "admin@setrip.id", isAdmin: true, }, // Organizers (akan di-link ke OrganizerVerification) { key: "dede", name: "Dede Inoen", email: "dede.inoen@setrip.id", profile: { bio: "Pendaki sejak 2015. Suka trip santai, bawa peserta pulang dengan selamat.", city: "Garut", interests: ["hiking", "camping", "fotografi"], instagram: "dede.inoen", vibe: "BALANCED", }, }, { key: "panji", name: "Panji Petualang", email: "panji@setrip.id", profile: { bio: "Trip leader 10+ tahun. Spesialis gunung & laut.", city: "Kuningan", interests: ["hiking", "diving", "snorkeling"], instagram: "panji.adv", vibe: "HARDCORE", }, }, { key: "fiersa", name: "Fiersa Besari", email: "fiersa@setrip.id", profile: { bio: "Buku, kopi, gunung. Kadang ngajak nonton konser bareng.", city: "Bandung", interests: ["hiking", "kuliner", "musik"], instagram: "fiersabesari", vibe: "CHILL", }, }, // Peserta { key: "budi", name: "Budi Santoso", email: "budi@gmail.com", profile: { bio: "Office worker yang lagi cari teman buat jalan akhir pekan.", city: "Jakarta", interests: ["hiking", "kuliner"], vibe: "BALANCED", }, }, { key: "sari", name: "Sari Dewi", email: "sari@gmail.com", profile: { bio: "Solo traveler. Suka pantai, ikan, dan tidur siang di hammock.", city: "Surabaya", interests: ["snorkeling", "yoga", "fotografi"], instagram: "saridew_id", vibe: "CHILL", }, }, { key: "doni", name: "Doni Prasetyo", email: "doni@gmail.com", profile: { bio: "Diver. Cari teman buddy fun-dive sebulan sekali.", city: "Denpasar", interests: ["diving", "freediving"], vibe: "HARDCORE", }, }, { key: "maya", name: "Maya Putri", email: "maya@gmail.com", profile: { bio: "Yoga teacher. Lagi belajar lebih hadir di setiap perjalanan.", city: "Yogyakarta", interests: ["yoga", "retreat", "kuliner"], instagram: "maya.putri", vibe: "CHILL", }, }, { key: "raka", name: "Raka Aditya", email: "raka@gmail.com", profile: { bio: "Anak baru di dunia trip. Energy tinggi, niat banyak.", city: "Bandung", interests: ["hiking", "konser"], vibe: "HARDCORE", }, }, ]; type UserMap = Record; async function seedUsers(): Promise { const password = await bcrypt.hash(SEED_PASSWORD, 12); const acceptedAt = new Date(); const map: UserMap = {}; for (const u of SEED_USERS) { const created = await prisma.user.create({ data: { name: u.name, email: u.email, password, emailVerified: acceptedAt, acceptedTermsAndPrivacy: true, acceptedAt, profile: u.profile ? { create: { bio: u.profile.bio, city: u.profile.city, interests: u.profile.interests ?? [], instagram: u.profile.instagram, vibe: u.profile.vibe, }, } : undefined, }, }); map[u.key] = { id: created.id, email: created.email, name: created.name }; } return map; } // ============================================================================ // 3. Organizer Verification — APPROVED, PENDING, REJECTED untuk demo admin queue // ============================================================================ interface SeedVerification { userKey: string; fullName: string; nik: string; birthDate: Date; address: string; bankName: string; bankAccountNumber: string; bankAccountName: string; status: "APPROVED" | "PENDING" | "REJECTED"; rejectionReason?: string; } const SEED_VERIFICATIONS: SeedVerification[] = [ { userKey: "dede", fullName: "Dede Inoen", nik: "3201010101010001", birthDate: utc(1990, 0, 1, 0, 0), address: "Jl. Pendaki No. 1, Garut, Jawa Barat", bankName: "BCA", bankAccountNumber: "1234567890", bankAccountName: "Dede Inoen", status: "APPROVED", }, { userKey: "panji", fullName: "Panji Petualang", nik: "3201010101010002", birthDate: utc(1985, 5, 15, 0, 0), address: "Jl. Adventure No. 7, Kuningan, Jawa Barat", bankName: "Mandiri", bankAccountNumber: "9876543210", bankAccountName: "Panji Petualang", status: "APPROVED", }, { userKey: "fiersa", fullName: "Fiersa Besari", nik: "3201010101010003", birthDate: utc(1988, 2, 3, 0, 0), address: "Jl. Cisitu No. 99, Bandung, Jawa Barat", bankName: "BNI", bankAccountNumber: "5566778899", bankAccountName: "Fiersa Besari", status: "APPROVED", }, // Demo admin queue: PENDING + REJECTED { userKey: "doni", fullName: "Doni Prasetyo", nik: "5101010202020001", birthDate: utc(1992, 8, 10, 0, 0), address: "Jl. Diving No. 12, Denpasar, Bali", bankName: "BCA", bankAccountNumber: "1112223334", bankAccountName: "Doni Prasetyo", status: "PENDING", }, { userKey: "raka", fullName: "Raka Aditya", nik: "3201010101010099", birthDate: utc(2000, 11, 31, 0, 0), address: "Alamat tidak jelas", bankName: "Bank lain", bankAccountNumber: "0001", bankAccountName: "RAKA", status: "REJECTED", rejectionReason: "Foto KTP tidak terbaca, alamat tidak lengkap.", }, ]; async function seedVerifications(users: UserMap, adminId: string) { const now = new Date(); for (const v of SEED_VERIFICATIONS) { const owner = users[v.userKey]; if (!owner) continue; const isApproved = v.status === "APPROVED"; const isReviewed = v.status !== "PENDING"; await prisma.organizerVerification.create({ data: { userId: owner.id, fullName: v.fullName, nikEncrypted: encryptString(v.nik), nikHash: hmacHex(v.nik), birthDate: v.birthDate, address: v.address, ktpImageKey: `ktp/seed-${v.userKey}.jpg`, livenessKey: `liveness/seed-${v.userKey}.jpg`, bankName: v.bankName, bankAccountNumber: v.bankAccountNumber, bankAccountName: v.bankAccountName, status: v.status, rejectionReason: v.rejectionReason, reviewedAt: isReviewed ? now : null, reviewedById: isReviewed ? adminId : null, verifiedAt: isApproved ? now : null, }, }); } } // ============================================================================ // 4. Trips — mix masa depan (OPEN) & masa lalu (COMPLETED) + 1 CLOSED. // Vibe diisi supaya filter & matching demo-able. // Trip masa lalu jadi pondasi review + trust score. // ============================================================================ interface SeedTripImage { url: string; caption: string; } interface SeedTrip { key: string; organizerKey: string; category: ActivityCategory; title: string; description: string; meetingPoint?: string; itineraryItems?: SeedItineraryItem[]; whatsIncluded?: string; whatsExcluded?: string; destination: string; location: string; date: Date; endDate: Date | null; maxParticipants: number; price: number; vibe: Vibe; status: "OPEN" | "FULL" | "CLOSED" | "COMPLETED"; images: SeedTripImage[]; } // 2026-05-10 = today (per CLAUDE.md). Trip masa lalu < hari ini, masa depan > hari ini. const SEED_TRIPS: SeedTrip[] = [ // ============ MASA LALU (sudah selesai — ada review) ============ { key: "past_papandayan", organizerKey: "dede", category: "HIKING", title: "Open Trip Papandayan — Maret 2026", description: `Pendakian santai ke Gunung Papandayan, batch Maret. Cocok untuk pemula.`, meetingPoint: "Alun-alun Garut, 05:00 WIB", itineraryItems: ITIN_PAPANDAYAN, destination: "Gunung Papandayan", location: "Garut, Jawa Barat", date: utc(2026, 2, 14, 22, 0), endDate: utc(2026, 2, 15, 18, 0), maxParticipants: 10, price: 250000, vibe: "BALANCED", status: "COMPLETED", images: [ { url: img("1554629947-334ff61d85dc"), caption: "Kawah Papandayan" }, { url: img("1464822759023-fed622ff2c3b"), caption: "Track menuju puncak" }, ], }, { key: "past_pahawang", organizerKey: "panji", category: "SNORKELING", title: "Snorkeling Pulau Pahawang — Februari 2026", description: `Trip snorkeling batch Februari. Cuaca bersahabat, visibility 10m+.`, meetingPoint: "Dermaga Ketapang, Lampung Selatan, 07:00 WIB", itineraryItems: ITIN_PAHAWANG, destination: "Pulau Pahawang Kecil", location: "Lampung Selatan, Lampung", date: utc(2026, 1, 21, 0, 0), endDate: null, maxParticipants: 10, price: 380000, vibe: "CHILL", status: "COMPLETED", images: [ { url: img("1583212292454-1fe6229603b7"), caption: "Snorkeling di Pahawang" }, ], }, { key: "past_culinary", organizerKey: "dede", category: "CULINARY", title: "Kulineran Street Food Bandung — April 2026", description: `Food tour batch April. 8 spot legend dilibas dalam satu hari.`, meetingPoint: "Stasiun Bandung pintu utara, 09:00 WIB", itineraryItems: ITIN_CULINARY, destination: "Street Food Tour Bandung", location: "Bandung, Jawa Barat", date: utc(2026, 3, 12, 0, 0), endDate: null, maxParticipants: 8, price: 175000, vibe: "CHILL", status: "COMPLETED", images: [ { url: img("1565299624946-b28f40a0ae38"), caption: "Street food" }, ], }, { key: "past_workshop_cancelled", organizerKey: "panji", category: "WORKSHOP", title: "Workshop Astrofotografi Bromo — Januari 2026 (BATAL)", description: `Cuaca tidak mendukung. Trip dibatalkan H-3, peserta refund 100%.`, meetingPoint: "-", destination: "Astrofotografi Bromo", location: "Probolinggo, Jawa Timur", date: utc(2026, 0, 18, 22, 0), endDate: utc(2026, 0, 19, 12, 0), maxParticipants: 6, price: 950000, vibe: "HARDCORE", status: "CLOSED", images: [], }, // ============ MASA DEPAN (OPEN — fitur join + bayar bisa di-test) ============ { key: "open_papandayan", organizerKey: "dede", category: "HIKING", title: "Open Trip Papandayan Weekend", description: `Pendakian santai ke Gunung Papandayan, cocok untuk pemula! ⚠️ Bawa: Sleeping bag, jaket, headlamp, air 2L`, meetingPoint: "Alun-alun Garut, Sabtu 05:00 WIB — detail grup WA.", itineraryItems: ITIN_PAPANDAYAN, whatsIncluded: `• Transport PP Garut–basecamp • Guide lokal • Tenda tim • Konsumsi 3x + snack`, whatsExcluded: `• Tiket masuk TN • Sleeping bag pribadi • Asuransi perjalanan`, destination: "Gunung Papandayan", location: "Garut, Jawa Barat", date: utc(2026, 5, 13, 22, 0), endDate: utc(2026, 5, 14, 18, 0), maxParticipants: 10, price: 250000, vibe: "BALANCED", status: "OPEN", images: [ { url: img("1554629947-334ff61d85dc"), caption: "Kawah Papandayan" }, { url: img("1464822759023-fed622ff2c3b"), caption: "Track menuju puncak" }, ], }, { key: "open_ciremai", organizerKey: "panji", category: "HIKING", title: "Pendakian Ciremai via Apuy", description: `Trip ke puncak tertinggi Jawa Barat! 🏔️ 📍 Meeting Point: Stasiun Kuningan, 04:00 WIB ⚠️ Level: Menengah — perlu stamina baik`, meetingPoint: "Stasiun Kuningan, Sabtu 04:00 WIB", itineraryItems: ITIN_CIREMAI, destination: "Gunung Ciremai", location: "Kuningan, Jawa Barat", date: utc(2026, 5, 23, 4, 0), endDate: utc(2026, 5, 24, 18, 0), maxParticipants: 8, price: 350000, vibe: "HARDCORE", status: "OPEN", images: [ { url: img("1480497490787-505ec076689f"), caption: "Puncak Ciremai 3.078 mdpl" }, { url: img("1502085671122-2d218cd434e6"), caption: "Sunrise dari puncak" }, ], }, { key: "open_camping", organizerKey: "dede", category: "CAMPING", title: "Camping Santai di Ranca Upas", description: `Camping bareng di tengah hutan pinus dengan ditemani rusa-rusa! Cocok buat first-timer. 📍 Meeting Point: Lembang, Sabtu 13:00 WIB 🎒 Fasilitas: Tenda dome, sleeping bag, BBQ, api unggun 🔥 Bonus: Live music akustik malam hari`, meetingPoint: "Pertigaan Pasar Lembang, Sabtu 13:00 WIB", itineraryItems: ITIN_CAMPING, whatsIncluded: `• Tenda + sleeping bag + matras • Logistik camp • Makan malam BBQ + sarapan • Tiket masuk lokasi`, whatsExcluded: `• Transport pribadi • Snack tambahan`, destination: "Ranca Upas", location: "Bandung Selatan, Jawa Barat", date: utc(2026, 5, 16, 6, 0), endDate: utc(2026, 5, 17, 12, 0), maxParticipants: 12, price: 220000, vibe: "CHILL", status: "OPEN", images: [ { url: img("1504280390367-361c6d9f38f4"), caption: "Tenda di hutan pinus" }, { url: img("1517824806704-9040b037703b"), caption: "Sunset di camp" }, ], }, { key: "open_snorkel", organizerKey: "panji", category: "SNORKELING", title: "Snorkeling Pulau Pahawang", description: `Air kristal-jernih, ikan warna-warni, dan pasir putih lembut. 🤿 Pemula friendly — guide profesional 📷 Underwater photo session included`, meetingPoint: "Dermaga Ketapang, Lampung Selatan, 07:00 WIB", itineraryItems: ITIN_PAHAWANG, whatsIncluded: `• Boat PP • Alat snorkel (masker, fin, life vest) • Guide & pemandu underwater • Konsumsi 2x`, whatsExcluded: `• Transport ke Lampung • Penginapan Sabtu malam`, destination: "Pulau Pahawang Kecil", location: "Lampung Selatan, Lampung", date: utc(2026, 5, 30, 0, 0), endDate: null, maxParticipants: 10, price: 380000, vibe: "CHILL", status: "OPEN", images: [ { url: img("1583212292454-1fe6229603b7"), caption: "Snorkeling di Pahawang" }, { url: img("1559825481-12a05cc00344"), caption: "Pasir putih Pahawang Kecil" }, ], }, { key: "open_diving", organizerKey: "fiersa", category: "DIVING", title: "Fun Dive Tulamben — USS Liberty Wreck", description: `Dive trip ke USS Liberty Wreck. ⚠️ Sertifikasi minimal: Open Water (PADI/SSI)`, meetingPoint: "Dive shop Tulamben, 06:30 WITA", itineraryItems: ITIN_DIVING, whatsIncluded: `• 2x dive guided • Full gear rental • Tank & weight • Konsumsi siang`, whatsExcluded: `• Transport ke Bali • Penginapan • Sertifikasi (cek validitas)`, destination: "USS Liberty Wreck", location: "Tulamben, Karangasem, Bali", date: utc(2026, 6, 4, 0, 0), endDate: utc(2026, 6, 5, 12, 0), maxParticipants: 6, price: 1850000, vibe: "HARDCORE", status: "OPEN", images: [ { url: img("1544551763-46a013bb70d5"), caption: "Wreck dive Tulamben" }, { url: img("1566024287286-457247b70310"), caption: "Reef Tulamben" }, ], }, { key: "open_islandhop", organizerKey: "panji", category: "ISLAND_HOPPING", title: "Karimun Jawa Island Hopping 3D2N", description: `Hopping 5 pulau favorit di Karimun Jawa. 🏝️ Cocok untuk solo traveler & couple`, meetingPoint: "Pelabuhan Kartini Jepara, Jumat 07:00 WIB", itineraryItems: ITIN_ISLANDHOP, whatsIncluded: `• Tiket kapal feri PP Jepara–Karimun • Homestay 2 malam (twin sharing) • Boat hopping 2 hari • Alat snorkel • Makan 6x`, whatsExcluded: `• Transport ke Jepara • Tiket pesawat`, destination: "Kepulauan Karimun Jawa", location: "Jepara, Jawa Tengah", date: utc(2026, 6, 12, 0, 0), endDate: utc(2026, 6, 14, 18, 0), maxParticipants: 12, price: 1450000, vibe: "BALANCED", status: "OPEN", images: [ { url: img("1507525428034-b723cf961d3e"), caption: "Pantai Karimun" }, { url: img("1519046904884-53103b34b206"), caption: "Boat hopping" }, ], }, { key: "open_citytrip", organizerKey: "fiersa", category: "CITY_TRIP", title: "City Trip Jogja Hidden Gems", description: `Bukan Malioboro lagi. Trip jelajah Jogja sisi 'lokal'. 🚐 Mobil grup, bukan tour bus`, meetingPoint: "Stasiun Tugu Yogyakarta, Sabtu 08:00 WIB", itineraryItems: ITIN_CITYTRIP, whatsIncluded: `• Transport mobil grup 2 hari • Tour leader lokal • Makan 3x (kuliner lokal) • Tiket masuk semua spot`, whatsExcluded: `• Transport ke Jogja • Penginapan (rekomendasi disediakan)`, destination: "Yogyakarta", location: "Yogyakarta", date: utc(2026, 5, 22, 0, 0), endDate: utc(2026, 5, 23, 20, 0), maxParticipants: 8, price: 650000, vibe: "BALANCED", status: "OPEN", images: [ { url: img("1596402184320-417e7178b2cd"), caption: "Tamansari" }, { url: img("1583309217394-d191d747bc66"), caption: "Sudut Jogja" }, ], }, { key: "open_culinary", organizerKey: "dede", category: "CULINARY", title: "Kulineran Street Food Bandung", description: `Hopping 8 spot kuliner legend Bandung dalam satu hari. 🍜 Cocok buat foodie & first-timer`, meetingPoint: "Stasiun Bandung pintu utara, 09:00 WIB", itineraryItems: ITIN_CULINARY, whatsIncluded: `• Transport angkot/grup • Tour leader food explorer • Sample setiap spot (8 tempat)`, whatsExcluded: `• Pembelian extra di luar sample`, destination: "Street Food Tour Bandung", location: "Bandung, Jawa Barat", date: utc(2026, 5, 17, 0, 0), endDate: null, maxParticipants: 8, price: 175000, vibe: "CHILL", status: "OPEN", images: [ { url: img("1565299624946-b28f40a0ae38"), caption: "Street food" }, ], }, { key: "open_concert", organizerKey: "fiersa", category: "CONCERT", title: "Nonton Coldplay Bareng — Music of the Spheres Jakarta", description: `Cari teman buat nonton Coldplay tapi gak mau nonton sendirian? Gabung grup ini. 🎤 Tiket BUKAN termasuk — peserta bawa tiket masing-masing 🤝 Grup hanya untuk koordinasi meet-up & after-party`, meetingPoint: "Plaza GBK, depan loket Cat 1, 17:00 WIB", itineraryItems: ITIN_CONCERT, whatsIncluded: `• Koordinasi grup • Foto bareng pre-show • After-party dinner di Senayan`, whatsExcluded: `• Tiket konser (bawa sendiri!) • Transport ke GBK`, destination: "Coldplay — Music of the Spheres", location: "Stadion Utama GBK, Jakarta", date: utc(2026, 6, 20, 10, 0), endDate: null, maxParticipants: 6, price: 0, // Free trip — booking langsung PAID saat di-confirm vibe: "BALANCED", status: "OPEN", images: [ { url: img("1470229722913-7c0e2dbbafd3"), caption: "Konser malam" }, ], }, { key: "open_workshop", organizerKey: "panji", category: "WORKSHOP", title: "Workshop Fotografi Lanskap — Pangalengan", description: `Belajar fotografi lanskap langsung di lapangan. 📷 Bawa kamera DSLR/mirrorless + tripod 👨‍🏫 Mentor: fotografer pro (10+ tahun pengalaman)`, meetingPoint: "Alun-alun Pangalengan, Sabtu 04:00 WIB", itineraryItems: ITIN_WORKSHOP, whatsIncluded: `• Materi workshop (briefing + on-field) • Tour leader & mentor • Penginapan villa 1 malam • Konsumsi 3x`, whatsExcluded: `• Kamera & tripod (bawa sendiri) • Transport ke Pangalengan`, destination: "Fotografi Lanskap", location: "Pangalengan, Bandung Selatan", date: utc(2026, 6, 6, 0, 0), endDate: utc(2026, 6, 7, 18, 0), maxParticipants: 6, price: 850000, vibe: "BALANCED", status: "OPEN", images: [ { url: img("1452587925148-ce544e77e70d"), caption: "Sunrise kebun teh" }, { url: img("1444080748397-f442aa95c3e5"), caption: "Workshop on-field" }, ], }, { key: "open_retreat", organizerKey: "fiersa", category: "RETREAT", title: "Mindfulness Retreat 3D — Ubud", description: `Retreat 3 hari di tengah sawah Ubud. 🧘 Untuk yang lagi burnout & butuh reset 👥 Grup kecil (max 8) — pengalaman akrab`, meetingPoint: "Villa Sawah Ubud (alamat dikirim H-3 via WA)", itineraryItems: ITIN_RETREAT, whatsIncluded: `• Penginapan villa 2 malam • Yoga 4 sesi + meditasi 6 sesi • Sound healing (1 sesi) • Konsumsi vegan 6x • Welcome kit (jurnal, herbal tea)`, whatsExcluded: `• Transport ke Ubud • Treatment spa opsional`, destination: "Mindfulness Retreat Ubud", location: "Ubud, Gianyar, Bali", date: utc(2026, 6, 26, 0, 0), endDate: utc(2026, 6, 28, 14, 0), maxParticipants: 8, price: 2400000, vibe: "CHILL", status: "OPEN", images: [ { url: img("1545389336-cf090694435e"), caption: "Yoga di sawah Ubud" }, { url: img("1518609878373-06d740f60d8b"), caption: "Villa retreat" }, ], }, ]; type TripMap = Record; async function seedTrips(users: UserMap): Promise { const map: TripMap = {}; for (const t of SEED_TRIPS) { const organizer = users[t.organizerKey]; if (!organizer) { throw new Error(`Organizer ${t.organizerKey} tidak ditemukan`); } const created = await prisma.trip.create({ data: { category: t.category, title: t.title, description: t.description, meetingPoint: t.meetingPoint, whatsIncluded: t.whatsIncluded, whatsExcluded: t.whatsExcluded, destination: t.destination, location: t.location, date: t.date, endDate: t.endDate, maxParticipants: t.maxParticipants, price: t.price, vibe: t.vibe, status: t.status, organizerId: organizer.id, images: t.images.length ? { create: t.images.map((im, order) => ({ url: im.url, caption: im.caption, order, })), } : undefined, itineraryItems: t.itineraryItems?.length ? { create: t.itineraryItems.map((item, order) => ({ day: item.day, startTime: item.startTime, endTime: item.endTime ?? null, activity: item.activity, order, })), } : undefined, }, }); map[t.key] = { id: created.id, price: created.price, status: t.status }; } return map; } // ============================================================================ // 5. Participants + Booking + Payment // Setiap baris = (tripKey, userKey, intent). `intent` mendeskripsikan // state akhir yang kita simulasikan, supaya UI menampilkan beragam kondisi. // // Mapping intent → (Participant, Booking, Payment): // - "PENDING_REQUEST" : Participant PENDING, Booking PENDING, no Payment // - "AWAITING_PAY" : Participant CONFIRMED, Booking AWAITING_PAY, no Payment // - "MARKED_PAID" : Participant CONFIRMED, Booking AWAITING_PAY, Payment AWAITING (peserta klik "sudah transfer") // - "PAID" : Participant CONFIRMED, Booking PAID, Payment PAID (organizer sudah konfirmasi) // - "FREE_CONFIRMED" : Participant CONFIRMED, Booking PAID, no Payment (trip gratis) // - "CANCELLED" : Participant CANCELLED, Booking CANCELLED, no Payment // ============================================================================ type Intent = | "PENDING_REQUEST" | "AWAITING_PAY" | "MARKED_PAID" | "PAID" | "FREE_CONFIRMED" | "CANCELLED"; interface SeedParticipant { tripKey: string; userKey: string; intent: Intent; } const SEED_PARTICIPANTS: SeedParticipant[] = [ // ---------- TRIP MASA LALU (COMPLETED) — semua PAID, jadi pondasi review ---------- { tripKey: "past_papandayan", userKey: "budi", intent: "PAID" }, { tripKey: "past_papandayan", userKey: "sari", intent: "PAID" }, { tripKey: "past_papandayan", userKey: "doni", intent: "PAID" }, { tripKey: "past_papandayan", userKey: "maya", intent: "PAID" }, { tripKey: "past_pahawang", userKey: "budi", intent: "PAID" }, { tripKey: "past_pahawang", userKey: "maya", intent: "PAID" }, { tripKey: "past_pahawang", userKey: "raka", intent: "PAID" }, { tripKey: "past_culinary", userKey: "sari", intent: "PAID" }, { tripKey: "past_culinary", userKey: "doni", intent: "PAID" }, { tripKey: "past_culinary", userKey: "raka", intent: "CANCELLED" }, // batal H-1 // CLOSED trip — peserta yang ke-refund (di-mark CANCELLED) { tripKey: "past_workshop_cancelled", userKey: "budi", intent: "CANCELLED" }, { tripKey: "past_workshop_cancelled", userKey: "doni", intent: "CANCELLED" }, // ---------- TRIP MASA DEPAN — mix state ---------- // Papandayan — campur semua state biar dashboard organizer kaya { tripKey: "open_papandayan", userKey: "budi", intent: "PAID" }, { tripKey: "open_papandayan", userKey: "sari", intent: "MARKED_PAID" }, { tripKey: "open_papandayan", userKey: "doni", intent: "AWAITING_PAY" }, { tripKey: "open_papandayan", userKey: "raka", intent: "PENDING_REQUEST" }, // Ciremai — 2 confirmed paid { tripKey: "open_ciremai", userKey: "budi", intent: "PAID" }, { tripKey: "open_ciremai", userKey: "maya", intent: "AWAITING_PAY" }, // Camping — 5 peserta variasi { tripKey: "open_camping", userKey: "budi", intent: "PAID" }, { tripKey: "open_camping", userKey: "sari", intent: "PAID" }, { tripKey: "open_camping", userKey: "doni", intent: "MARKED_PAID" }, { tripKey: "open_camping", userKey: "maya", intent: "AWAITING_PAY" }, { tripKey: "open_camping", userKey: "raka", intent: "PENDING_REQUEST" }, // Snorkeling { tripKey: "open_snorkel", userKey: "sari", intent: "PAID" }, { tripKey: "open_snorkel", userKey: "maya", intent: "MARKED_PAID" }, { tripKey: "open_snorkel", userKey: "raka", intent: "PENDING_REQUEST" }, // Diving — mahal, sedikit peserta { tripKey: "open_diving", userKey: "doni", intent: "AWAITING_PAY" }, // Island hop { tripKey: "open_islandhop", userKey: "budi", intent: "PAID" }, { tripKey: "open_islandhop", userKey: "sari", intent: "PAID" }, { tripKey: "open_islandhop", userKey: "maya", intent: "MARKED_PAID" }, { tripKey: "open_islandhop", userKey: "raka", intent: "PENDING_REQUEST" }, // City trip { tripKey: "open_citytrip", userKey: "budi", intent: "PAID" }, { tripKey: "open_citytrip", userKey: "maya", intent: "PAID" }, { tripKey: "open_citytrip", userKey: "sari", intent: "PENDING_REQUEST" }, // Culinary — penuh { tripKey: "open_culinary", userKey: "budi", intent: "PAID" }, { tripKey: "open_culinary", userKey: "sari", intent: "PAID" }, { tripKey: "open_culinary", userKey: "doni", intent: "PAID" }, { tripKey: "open_culinary", userKey: "maya", intent: "PAID" }, { tripKey: "open_culinary", userKey: "raka", intent: "PAID" }, // Concert (FREE) — Booking langsung PAID { tripKey: "open_concert", userKey: "maya", intent: "FREE_CONFIRMED" }, { tripKey: "open_concert", userKey: "raka", intent: "FREE_CONFIRMED" }, { tripKey: "open_concert", userKey: "sari", intent: "PENDING_REQUEST" }, // Workshop { tripKey: "open_workshop", userKey: "doni", intent: "PAID" }, { tripKey: "open_workshop", userKey: "sari", intent: "AWAITING_PAY" }, // Retreat — niche { tripKey: "open_retreat", userKey: "maya", intent: "PENDING_REQUEST" }, ]; interface BookingPlan { participantStatus: "PENDING" | "CONFIRMED" | "CANCELLED"; bookingStatus: "PENDING" | "AWAITING_PAY" | "PAID" | "CANCELLED"; payment: null | { status: "AWAITING" | "PAID" }; markedPaidAt: Date | null; paymentConfirmedAt: Date | null; } function planFromIntent(intent: Intent, now: Date): BookingPlan { switch (intent) { case "PENDING_REQUEST": return { participantStatus: "PENDING", bookingStatus: "PENDING", payment: null, markedPaidAt: null, paymentConfirmedAt: null, }; case "AWAITING_PAY": return { participantStatus: "CONFIRMED", bookingStatus: "AWAITING_PAY", payment: null, markedPaidAt: null, paymentConfirmedAt: null, }; case "MARKED_PAID": return { participantStatus: "CONFIRMED", bookingStatus: "AWAITING_PAY", payment: { status: "AWAITING" }, markedPaidAt: now, paymentConfirmedAt: null, }; case "PAID": return { participantStatus: "CONFIRMED", bookingStatus: "PAID", payment: { status: "PAID" }, markedPaidAt: now, paymentConfirmedAt: now, }; case "FREE_CONFIRMED": return { participantStatus: "CONFIRMED", bookingStatus: "PAID", payment: null, markedPaidAt: null, paymentConfirmedAt: null, }; case "CANCELLED": return { participantStatus: "CANCELLED", bookingStatus: "CANCELLED", payment: null, markedPaidAt: null, paymentConfirmedAt: null, }; } } async function seedParticipants(trips: TripMap, users: UserMap) { const now = new Date(); for (const sp of SEED_PARTICIPANTS) { const trip = trips[sp.tripKey]; const user = users[sp.userKey]; if (!trip || !user) continue; const plan = planFromIntent(sp.intent, now); const participant = await prisma.tripParticipant.create({ data: { tripId: trip.id, userId: user.id, status: plan.participantStatus, markedPaidAt: plan.markedPaidAt, paymentConfirmedAt: plan.paymentConfirmedAt, }, }); const booking = await prisma.booking.create({ data: { tripId: trip.id, userId: user.id, participantId: participant.id, amount: trip.price, status: plan.bookingStatus, }, }); if (plan.payment) { const data: Prisma.PaymentUncheckedCreateInput = { bookingId: booking.id, provider: "MANUAL", externalOrderId: `manual-${booking.id}`, amount: trip.price, status: plan.payment.status, method: "manual_transfer", paidAt: plan.payment.status === "PAID" ? now : null, }; await prisma.payment.create({ data }); } } } // ============================================================================ // 6. Reviews — hanya untuk trip COMPLETED, dari peserta CONFIRMED non-organizer. // ============================================================================ interface SeedReview { tripKey: string; userKey: string; rating: number; comment: string; } const SEED_REVIEWS: SeedReview[] = [ { tripKey: "past_papandayan", userKey: "budi", rating: 5, comment: "Trip-nya santai tapi tetep seru. Guide-nya sabar banget sama yang baru pertama mendaki.", }, { tripKey: "past_papandayan", userKey: "sari", rating: 5, comment: "Worth it banget! Sunrise di Tegal Alun cakep, makanan juga oke.", }, { tripKey: "past_papandayan", userKey: "doni", rating: 4, comment: "Trip aman dan terorganisir. Kurangin lama briefing pagi aja.", }, { tripKey: "past_papandayan", userKey: "maya", rating: 5, comment: "Bang Dede bener-bener jaga energi grup. Highly recommended.", }, { tripKey: "past_pahawang", userKey: "budi", rating: 4, comment: "Spot snorkeling-nya bagus, ikan rame. Kapal agak lama nunggu di dermaga.", }, { tripKey: "past_pahawang", userKey: "maya", rating: 5, comment: "Pertama kali snorkel & gak takut karena guide-nya stand-by. Mantap!", }, { tripKey: "past_pahawang", userKey: "raka", rating: 5, comment: "Sunset Pahawang Kecil top. Bakal ikut lagi.", }, { tripKey: "past_culinary", userKey: "sari", rating: 4, comment: "8 spot dalam 1 hari, perut sampe penuh. Mie kocoknya juara.", }, { tripKey: "past_culinary", userKey: "doni", rating: 3, comment: "Seru tapi rute pagi-siang kepadetan, jadwal molor 1 jam.", }, ]; async function seedReviews(trips: TripMap, users: UserMap) { for (const r of SEED_REVIEWS) { const trip = trips[r.tripKey]; const user = users[r.userKey]; if (!trip || !user) continue; await prisma.tripReview.create({ data: { tripId: trip.id, userId: user.id, rating: r.rating, comment: r.comment, }, }); } } // ============================================================================ // Main // ============================================================================ async function main() { console.log("🌱 Seeding database...\n"); console.log("⏳ Cleanup tabel lama..."); await cleanup(); console.log("⏳ Seed users + profile..."); const users = await seedUsers(); const admin = users["admin"]; if (!admin) throw new Error("Admin user gagal dibuat"); console.log("⏳ Seed organizer verifications..."); await seedVerifications(users, admin.id); console.log("⏳ Seed trips..."); const trips = await seedTrips(users); console.log("⏳ Seed participants + bookings + payments..."); await seedParticipants(trips, users); console.log("⏳ Seed reviews..."); await seedReviews(trips, users); console.log(` ✅ Selesai. Ringkasan: ${SEED_USERS.length} users (1 admin, 3 organizer, ${SEED_USERS.length - 4} peserta) ${SEED_VERIFICATIONS.length} organizer verifications (3 APPROVED, 1 PENDING, 1 REJECTED) ${SEED_TRIPS.length} trips (3 COMPLETED + 1 CLOSED + ${SEED_TRIPS.filter((t) => t.status === "OPEN").length} OPEN) ${SEED_PARTICIPANTS.length} participants (mix PENDING/CONFIRMED/CANCELLED) ${SEED_REVIEWS.length} reviews 🔐 Login info: Admin : admin@setrip.id Organizer : dede.inoen@setrip.id, panji@setrip.id, fiersa@setrip.id Peserta : budi/sari/doni/maya/raka @gmail.com Password : ${SEED_PASSWORD} `); } main() .catch((e) => { console.error("❌ Seed failed:", e); process.exit(1); }) .finally(async () => { await prisma.$disconnect(); });