import "dotenv/config"; import { PrismaClient } from "../app/generated/prisma/client"; 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 }); async function main() { console.log("🌱 Seeding database...\n"); // Clean existing data (order matters for FK) await prisma.tripReview.deleteMany(); await prisma.tripParticipant.deleteMany(); await prisma.tripImage.deleteMany(); await prisma.trip.deleteMany(); await prisma.organizerVerification.deleteMany(); await prisma.user.deleteMany(); // ==================== USERS ==================== const password = await bcrypt.hash("password123", 12); const dede = await prisma.user.create({ data: { name: "Dede Inoen", email: "dede.inoen@setrip.id", password }, }); const panji = await prisma.user.create({ data: { name: "Panji Petualang", email: "panji@setrip.id", password }, }); const fiersa = await prisma.user.create({ data: { name: "Fiersa Besari", email: "fiersa@setrip.id", password }, }); const budi = await prisma.user.create({ data: { name: "Budi Santoso", email: "budi@gmail.com", password }, }); const sari = await prisma.user.create({ data: { name: "Sari Dewi", email: "sari@gmail.com", password }, }); const doni = await prisma.user.create({ data: { name: "Doni Prasetyo", email: "doni@gmail.com", password }, }); const maya = await prisma.user.create({ data: { name: "Maya Putri", email: "maya@gmail.com", password }, }); const raka = await prisma.user.create({ data: { name: "Raka Aditya", email: "raka@gmail.com", password }, }); console.log("βœ… Users created"); console.log(" Organizer: dede.inoen@setrip.id, panji@setrip.id, fiersa@setrip.id"); console.log(" Peserta: budi, sari, doni, maya, raka @gmail.com"); console.log(" Password semua: password123\n"); // ==================== ORGANIZER VERIFICATIONS ==================== const verifiedAt = new Date(); const dedeNik = "3201010101010001"; const panjiNik = "3201010101010002"; await prisma.organizerVerification.createMany({ data: [ { userId: dede.id, fullName: "Dede Inoen", nikEncrypted: encryptString(dedeNik), nikHash: hmacHex(dedeNik), birthDate: new Date(Date.UTC(1990, 0, 1)), address: "Jl. Pendaki No. 1, Garut, Jawa Barat", ktpImageKey: "ktp/seed-dede.jpg", livenessKey: "liveness/seed-dede.jpg", bankName: "BCA", bankAccountNumber: "1234567890", bankAccountName: "Dede Inoen", status: "APPROVED", reviewedAt: verifiedAt, verifiedAt, }, { userId: panji.id, fullName: "Panji Petualang", nikEncrypted: encryptString(panjiNik), nikHash: hmacHex(panjiNik), birthDate: new Date(Date.UTC(1985, 5, 15)), address: "Jl. Adventure No. 7, Kuningan, Jawa Barat", ktpImageKey: "ktp/seed-panji.jpg", livenessKey: "liveness/seed-panji.jpg", bankName: "Mandiri", bankAccountNumber: "9876543210", bankAccountName: "Panji Petualang", status: "APPROVED", reviewedAt: verifiedAt, verifiedAt, }, ], }); console.log("βœ… OrganizerVerification (APPROVED) untuk Dede & Panji\n"); // ==================== TRIPS + IMAGES ==================== /** * Tanggal disimpan eksplisit di UTC. Multi hari isi `endDate` (hari terakhir * trip). Trip 1 hari / night-event: `endDate` null. */ const utc = (y: number, m0: number, d: number, h = 12, min = 0) => new Date(Date.UTC(y, m0, d, h, min, 0, 0)); // Unsplash CDN photo helper. Slug ID di komentar = id di unsplash.com/photos/{slug}. const img = (id: string) => `https://images.unsplash.com/photo-${id}?w=1200&q=80&auto=format&fit=crop`; // --- HIKING: Papandayan --- const tripHiking = await prisma.trip.create({ data: { 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.", itinerary: `Sabtu β€’ 05:00 Meeting & briefing β€’ 07:00 Berangkat menuju basecamp β€’ 15:00 Camp area Pondok Salada Minggu β€’ 04:00 Summit attack β€’ 11:00 Turun β€’ 16:00 Estimasi kembali ke Garut`, 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, status: "OPEN", organizerId: dede.id, images: { create: [ { url: img("1554629947-334ff61d85dc"), caption: "Kawah Papandayan", order: 0 }, { url: img("1464822759023-fed622ff2c3b"), caption: "Track menuju puncak", order: 1 }, ], }, }, }); // --- HIKING: Ciremai --- const tripHikingCiremai = await prisma.trip.create({ data: { 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`, 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, status: "OPEN", organizerId: panji.id, images: { create: [ { url: img("1480497490787-505ec076689f"), caption: "Puncak Ciremai 3.078 mdpl", order: 0 }, { url: img("1502085671122-2d218cd434e6"), caption: "Sunrise dari puncak", order: 1 }, ], }, }, }); // --- CAMPING: Ranca Upas --- const tripCamping = await prisma.trip.create({ data: { 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", 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, status: "OPEN", organizerId: dede.id, images: { create: [ { url: img("1504280390367-361c6d9f38f4"), caption: "Tenda di hutan pinus", order: 0 }, { url: img("1517824806704-9040b037703b"), caption: "Sunset di camp", order: 1 }, ], }, }, }); // --- SNORKELING: Pulau Pahawang --- const tripSnorkel = await prisma.trip.create({ data: { category: "SNORKELING", title: "Snorkeling Pulau Pahawang", description: `Air kristal-jernih, ikan warna-warni, dan pasir putih lembut. Spot terumbu karang Pahawang Kecil masuk daftar must-visit di Lampung. 🀿 Pemula friendly β€” guide profesional πŸ“· Underwater photo session included`, meetingPoint: "Dermaga Ketapang, Lampung Selatan, 07:00 WIB", 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, status: "OPEN", organizerId: panji.id, images: { create: [ { url: img("1583212292454-1fe6229603b7"), caption: "Snorkeling di Pahawang", order: 0 }, { url: img("1559825481-12a05cc00344"), caption: "Pasir putih Pahawang Kecil", order: 1 }, ], }, }, }); // --- DIVING: Tulamben --- const tripDiving = await prisma.trip.create({ data: { category: "DIVING", title: "Fun Dive Tulamben β€” USS Liberty Wreck", description: `Dive trip ke USS Liberty Wreck, salah satu wreck dive paling ikonik di dunia. Visibility tinggi, kedalaman ramah open water diver. ⚠️ Sertifikasi minimal: Open Water (PADI/SSI)`, meetingPoint: "Dive shop Tulamben, 06:30 WITA", 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, status: "OPEN", organizerId: fiersa.id, images: { create: [ { url: img("1544551763-46a013bb70d5"), caption: "Wreck dive Tulamben", order: 0 }, { url: img("1566024287286-457247b70310"), caption: "Reef Tulamben", order: 1 }, ], }, }, }); // --- ISLAND HOPPING: Karimun Jawa --- const tripIslandHop = await prisma.trip.create({ data: { category: "ISLAND_HOPPING", title: "Karimun Jawa Island Hopping 3D2N", description: `Hopping 5 pulau favorit di Karimun Jawa: Menjangan Kecil, Menjangan Besar, Cemara, Cilik, Geleang. Snorkeling, sunset, dan barbeque tepi pantai. 🏝️ Cocok untuk solo traveler & couple`, meetingPoint: "Pelabuhan Kartini Jepara, Jumat 07:00 WIB", 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, status: "OPEN", organizerId: panji.id, images: { create: [ { url: img("1507525428034-b723cf961d3e"), caption: "Pantai Karimun", order: 0 }, { url: img("1519046904884-53103b34b206"), caption: "Boat hopping", order: 1 }, ], }, }, }); // --- CITY TRIP: Yogyakarta --- const tripCityTrip = await prisma.trip.create({ data: { category: "CITY_TRIP", title: "City Trip Jogja Hidden Gems", description: `Bukan Malioboro lagi. Trip jelajah Jogja sisi 'lokal' β€” Kotagede, Tamansari, Kalibiru, sampai angkringan andalan warga. 🚐 Mobil grup, bukan tour bus`, meetingPoint: "Stasiun Tugu Yogyakarta, Sabtu 08:00 WIB", 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, status: "OPEN", organizerId: fiersa.id, images: { create: [ { url: img("1596402184320-417e7178b2cd"), caption: "Tamansari", order: 0 }, { url: img("1583309217394-d191d747bc66"), caption: "Sudut Jogja", order: 1 }, ], }, }, }); // --- CULINARY: Bandung Street Food --- const tripCulinary = await prisma.trip.create({ data: { category: "CULINARY", title: "Kulineran Street Food Bandung", description: `Hopping 8 spot kuliner legend Bandung dalam satu hari: Surabi Enhaii, Mie Kocok Mang Dadeng, Lotek Kalipah Apo, sampai Es Cendol Elizabeth. 🍜 Cocok buat foodie & first-timer`, meetingPoint: "Stasiun Bandung pintu utara, 09:00 WIB", 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, status: "OPEN", organizerId: dede.id, images: { create: [ { url: img("1565299624946-b28f40a0ae38"), caption: "Street food", order: 0 }, ], }, }, }); // --- CONCERT: Coldplay Jakarta --- const tripConcert = await prisma.trip.create({ data: { 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", 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, status: "OPEN", organizerId: fiersa.id, images: { create: [ { url: img("1470229722913-7c0e2dbbafd3"), caption: "Konser malam", order: 0 }, ], }, }, }); // --- WORKSHOP: Fotografi Lanskap --- const tripWorkshop = await prisma.trip.create({ data: { category: "WORKSHOP", title: "Workshop Fotografi Lanskap β€” Pangalengan", description: `Belajar fotografi lanskap langsung di lapangan. Sunrise di kebun teh, golden hour di danau, milky way di malam hari (cuaca permitting). πŸ“· Bawa kamera DSLR/mirrorless + tripod πŸ‘¨β€πŸ« Mentor: fotografer pro (10+ tahun pengalaman)`, meetingPoint: "Alun-alun Pangalengan, Sabtu 04:00 WIB", 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, status: "OPEN", organizerId: panji.id, images: { create: [ { url: img("1452587925148-ce544e77e70d"), caption: "Sunrise kebun teh", order: 0 }, { url: img("1444080748397-f442aa95c3e5"), caption: "Workshop on-field", order: 1 }, ], }, }, }); // --- RETREAT: Mindfulness Ubud --- const tripRetreat = await prisma.trip.create({ data: { category: "RETREAT", title: "Mindfulness Retreat 3D β€” Ubud", description: `Retreat 3 hari di tengah sawah Ubud. Yoga pagi, meditasi guided, journaling, dan sound healing. 🧘 Untuk yang lagi burnout & butuh reset πŸ‘₯ Grup kecil (max 8) β€” pengalaman akrab`, meetingPoint: "Villa Sawah Ubud (alamat dikirim H-3 via WA)", 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, status: "OPEN", organizerId: fiersa.id, images: { create: [ { url: img("1545389336-cf090694435e"), caption: "Yoga di sawah Ubud", order: 0 }, { url: img("1518609878373-06d740f60d8b"), caption: "Villa retreat", order: 1 }, ], }, }, }); console.log("βœ… 11 Trips dibuat (10 kategori, semua tanggal masa depan)\n"); // ==================== PARTICIPANTS ==================== await prisma.tripParticipant.createMany({ data: [ // Hiking Papandayan β€” 4 peserta { tripId: tripHiking.id, userId: budi.id, status: "CONFIRMED" }, { tripId: tripHiking.id, userId: sari.id, status: "CONFIRMED" }, { tripId: tripHiking.id, userId: doni.id, status: "CONFIRMED" }, { tripId: tripHiking.id, userId: raka.id, status: "CONFIRMED" }, // Hiking Ciremai β€” 2 peserta { tripId: tripHikingCiremai.id, userId: budi.id, status: "CONFIRMED" }, { tripId: tripHikingCiremai.id, userId: maya.id, status: "CONFIRMED" }, // Camping β€” 5 peserta { tripId: tripCamping.id, userId: budi.id, status: "CONFIRMED" }, { tripId: tripCamping.id, userId: sari.id, status: "CONFIRMED" }, { tripId: tripCamping.id, userId: doni.id, status: "CONFIRMED" }, { tripId: tripCamping.id, userId: maya.id, status: "CONFIRMED" }, { tripId: tripCamping.id, userId: raka.id, status: "CONFIRMED" }, // Snorkeling β€” 3 peserta { tripId: tripSnorkel.id, userId: sari.id, status: "CONFIRMED" }, { tripId: tripSnorkel.id, userId: maya.id, status: "CONFIRMED" }, { tripId: tripSnorkel.id, userId: raka.id, status: "PENDING" }, // Diving β€” 1 peserta (paid trip, mahal) { tripId: tripDiving.id, userId: doni.id, status: "PENDING" }, // Island hopping β€” 4 peserta { tripId: tripIslandHop.id, userId: budi.id, status: "CONFIRMED" }, { tripId: tripIslandHop.id, userId: sari.id, status: "CONFIRMED" }, { tripId: tripIslandHop.id, userId: maya.id, status: "CONFIRMED" }, { tripId: tripIslandHop.id, userId: raka.id, status: "PENDING" }, // City trip β€” 3 peserta { tripId: tripCityTrip.id, userId: budi.id, status: "CONFIRMED" }, { tripId: tripCityTrip.id, userId: maya.id, status: "CONFIRMED" }, { tripId: tripCityTrip.id, userId: sari.id, status: "PENDING" }, // Culinary β€” 5 peserta { tripId: tripCulinary.id, userId: budi.id, status: "CONFIRMED" }, { tripId: tripCulinary.id, userId: sari.id, status: "CONFIRMED" }, { tripId: tripCulinary.id, userId: doni.id, status: "CONFIRMED" }, { tripId: tripCulinary.id, userId: maya.id, status: "CONFIRMED" }, { tripId: tripCulinary.id, userId: raka.id, status: "CONFIRMED" }, // Concert β€” 2 peserta { tripId: tripConcert.id, userId: maya.id, status: "CONFIRMED" }, { tripId: tripConcert.id, userId: raka.id, status: "CONFIRMED" }, // Workshop β€” 2 peserta { tripId: tripWorkshop.id, userId: doni.id, status: "CONFIRMED" }, { tripId: tripWorkshop.id, userId: sari.id, status: "CONFIRMED" }, // Retreat β€” 1 peserta (mahal, niche) { tripId: tripRetreat.id, userId: maya.id, status: "PENDING" }, ], }); console.log("βœ… Participants joined (mix CONFIRMED & PENDING per trip)\n"); console.log("πŸŽ‰ Seed complete!"); } main() .catch((e) => { console.error("❌ Seed failed:", e); process.exit(1); }) .finally(async () => { await prisma.$disconnect(); });