diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 45694aa..d32fbc6 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -5,7 +5,8 @@ "Bash(npx prisma *)", "Bash(Get-ChildItem -Path \"c:\\\\development\\\\DIOS\\\\weekly-project\\\\setrip\" -Force)", "Bash(Select-Object Name, PSIsContainer)", - "Bash(npx tsc *)" + "Bash(npx tsc *)", + "Bash(echo \"exitcode=$?\")" ] } } diff --git a/prisma/seed.ts b/prisma/seed.ts index f276bc1..a4c2521 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -1,5 +1,10 @@ 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"; @@ -9,530 +14,1346 @@ const adapter = new PrismaPg({ }); const prisma = new PrismaClient({ adapter }); -async function main() { - console.log("🌱 Seeding database...\n"); +// ============================================================================ +// Helpers +// ============================================================================ - // Clean existing data (order matters for FK) +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 β€” disimpan sebagai const supaya data trip tetap padat. +// Format: header `Hari N β€” ` lalu bullet `β€’ HH:MM–HH:MM aktivitas`. +// Lokasi, pos, dan spot diambil dari kasus nyata (Papandayan via Cisurupan, +// Ciremai via Apuy, USS Liberty Tulamben, Karimun Jawa hopping, dst). +// ============================================================================ + +const ITIN_PAPANDAYAN = `Hari 1 β€” Sabtu +β€’ 05:00–05:30 Meeting & briefing di Alun-alun Garut +β€’ 05:30–07:00 Perjalanan menuju basecamp Cisurupan +β€’ 07:00–08:00 Sarapan + repacking + pemanasan +β€’ 08:00–11:00 Trekking Camp David β€” Hutan Mati β€” Pondok Salada +β€’ 11:00–12:30 Setup tenda di Pondok Salada +β€’ 12:30–14:00 ISHOMA + games kenalan grup +β€’ 14:00–17:00 Eksplor Tegal Alun & Hutan Mati (golden hour foto) +β€’ 17:00–19:00 Masak bareng + makan malam +β€’ 19:00–21:00 Api unggun, kopi, sharing rencana summit +β€’ 21:00 Istirahat + +Hari 2 β€” Minggu +β€’ 03:30–04:00 Bangun + sarapan ringan + air panas +β€’ 04:00–05:30 Summit attack ke puncak Papandayan +β€’ 05:30–07:00 Sunrise + foto bareng di puncak +β€’ 07:00–09:00 Turun ke camp + sarapan utama +β€’ 09:00–11:00 Beres-beres tenda + repacking +β€’ 11:00–13:30 Turun ke basecamp Cisurupan +β€’ 13:30–14:30 Bersih-bersih + makan siang +β€’ 14:30–16:30 Perjalanan kembali ke Garut +β€’ 16:30 Sampai Garut, bubar grup`; + +const ITIN_CIREMAI = `Hari 1 β€” Sabtu +β€’ 04:00–04:30 Meeting & briefing di Stasiun Kuningan +β€’ 04:30–06:30 Perjalanan ke basecamp Apuy via Maja & Argapura +β€’ 06:30–07:30 Sarapan + registrasi simaksi + repacking +β€’ 07:30–10:30 Trek Pos 1 (Berod) β†’ Pos 2 (Arban) β†’ Pos 3 (Tegal Masawa) +β€’ 10:30–11:30 ISHOMA di Pos 4 (Tegal Jamuju) +β€’ 11:30–14:30 Lanjut Pos 5 (Sanghyang Rangkah) β†’ Pos 6 (Goa Walet) +β€’ 14:30–16:00 Setup tenda di Pos 6 +β€’ 16:00–18:00 Acara bebas + makan sore + persiapan summit +β€’ 18:00–20:00 Briefing summit + early dinner +β€’ 20:00 Istirahat (bangun dini hari) + +Hari 2 β€” Minggu +β€’ 02:00–02:30 Bangun + cemilan + minuman hangat +β€’ 02:30–05:00 Summit attack ke puncak Sunan Cirebon (3.078 mdpl) +β€’ 05:00–06:30 Sunrise di puncak Ciremai +β€’ 06:30–08:30 Turun ke Pos 6 + sarapan +β€’ 08:30–10:30 Beres tenda + repacking +β€’ 10:30–14:00 Turun ke basecamp Apuy (track curam β€” hati-hati lutut) +β€’ 14:00–15:00 Bersih-bersih + makan siang di basecamp +β€’ 15:00–17:00 Kembali ke Stasiun Kuningan +β€’ 17:00 Bubar grup`; + +const ITIN_CAMPING = `Hari 1 β€” Sabtu +β€’ 13:00–13:30 Meeting di Pertigaan Pasar Lembang +β€’ 13:30–15:00 Perjalanan ke Ranca Upas via Ciwidey +β€’ 15:00–16:00 Check-in + setup tenda dome (sudah disiapkan tim) +β€’ 16:00–17:30 Tour camp area + ketemu rusa-rusa +β€’ 17:30–19:00 Persiapan BBQ + nyalakan api unggun +β€’ 19:00–21:00 Makan malam BBQ +β€’ 21:00–23:00 Live music akustik + games +β€’ 23:00 Istirahat + +Hari 2 β€” Minggu +β€’ 06:00–07:00 Sunrise + foto di hutan pinus +β€’ 07:00–08:30 Sarapan (nasi goreng + roti bakar + kopi) +β€’ 08:30–10:00 Memberi makan rusa + sesi foto +β€’ 10:00–11:00 Beres tenda + bersih-bersih +β€’ 11:00–11:30 Pulang menuju Lembang +β€’ 12:00 Sampai Lembang, bubar grup`; + +const ITIN_PAHAWANG = `Hari 1 β€” Sabtu (one-day trip) +β€’ 07:00–07:30 Meeting di Dermaga Ketapang, Lampung Selatan +β€’ 07:30–08:30 Briefing safety + fitting alat snorkel +β€’ 08:30–09:30 Sailing menuju Pulau Pahawang Kecil +β€’ 09:30–11:30 Snorkeling spot Cukuh Bedil β€” terumbu warna-warni +β€’ 11:30–12:30 Pindah spot ke Pulau Kelagian Kecil +β€’ 12:30–14:00 Makan siang + istirahat di pasir putih +β€’ 14:00–15:30 Snorkeling Tanjung Putus + sesi foto underwater +β€’ 15:30–16:30 Sailing kembali ke dermaga +β€’ 16:30–17:00 Bersih-bersih + bubar grup`; + +const ITIN_DIVING = `Hari 1 β€” Sabtu +β€’ 06:30–07:00 Meeting di dive shop Tulamben + welcome coffee +β€’ 07:00–08:00 Briefing dive plan + cek sertifikasi + fitting gear +β€’ 08:00–09:00 Surface interval + pengecekan tank/regulator +β€’ 09:00–10:30 Dive #1 β€” USS Liberty Wreck (5–28m, ~50 menit bottom time) +β€’ 10:30–12:00 Surface interval + brunch + log dive +β€’ 12:00–13:30 Dive #2 β€” Coral Garden / Drop Off (~50 menit) +β€’ 13:30–15:00 Debrief + makan siang +β€’ 15:00–17:00 Acara bebas (rest atau eksplor desa Tulamben) +β€’ 17:00–19:00 Sunset di pantai Tulamben + dinner +β€’ 19:00 Istirahat di homestay (mandiri) + +Hari 2 β€” Minggu +β€’ 06:00–06:30 Bangun + kopi +β€’ 06:30–07:30 Briefing dive #3 (early morning visibility tinggi) +β€’ 07:30–09:00 Dive #3 β€” Liberty Wreck pagi +β€’ 09:00–10:30 Surface interval + sarapan + log +β€’ 10:30–12:00 Dive #4 (opsional, fun dive shallow reef) +β€’ 12:00–13:00 Bersih gear + debrief akhir +β€’ 13:00–14:00 Makan siang penutupan +β€’ 14:00 Bubar grup`; + +const ITIN_ISLANDHOP = `Hari 1 β€” Jumat +β€’ 07:00–07:30 Meeting di Pelabuhan Kartini Jepara +β€’ 07:30–13:00 Penyeberangan kapal feri Jepara β†’ Karimun Jawa +β€’ 13:00–14:00 Tiba di Pelabuhan Karimun + transfer homestay +β€’ 14:00–15:00 Check-in homestay + ISHOMA +β€’ 15:00–17:30 Sunset di Bukit Love + foto-foto +β€’ 17:30–19:00 Bersih-bersih + makan malam +β€’ 19:00–21:00 Alun-alun Karimun + jajan kuliner +β€’ 21:00 Istirahat + +Hari 2 β€” Sabtu +β€’ 06:30–07:30 Sarapan + briefing hopping +β€’ 07:30–09:30 Boat ke Pulau Menjangan Kecil β€” snorkeling spot terumbu +β€’ 09:30–11:30 Pulau Menjangan Besar β€” interaksi hiu (penangkaran) +β€’ 11:30–13:00 Makan siang BBQ ikan di Pulau Cemara Besar +β€’ 13:00–15:00 Pulau Cemara Kecil + foto pasir putih +β€’ 15:00–17:00 Pulau Cilik β€” sunset + snorkel terakhir +β€’ 17:00–18:30 Kembali ke homestay + bersih-bersih +β€’ 18:30–20:00 Makan malam seafood +β€’ 20:00 Acara bebas + +Hari 3 β€” Minggu +β€’ 06:00–07:00 Sunrise di Tanjung Gelam +β€’ 07:00–09:00 Sarapan + pack-up +β€’ 09:00–10:00 Belanja oleh-oleh di pelabuhan +β€’ 10:00–16:00 Penyeberangan kapal feri Karimun β†’ Jepara +β€’ 16:00–17:00 Tiba di Pelabuhan Kartini, bubar grup`; + +const ITIN_CITYTRIP = `Hari 1 β€” Sabtu +β€’ 08:00–08:30 Meeting di Stasiun Tugu Yogyakarta +β€’ 08:30–10:00 Sarapan Gudeg Yu Djum + briefing rute +β€’ 10:00–12:00 Kotagede β€” kerajinan perak + Masjid Mataram +β€’ 12:00–13:30 Makan siang Sate Klathak Pak Pong +β€’ 13:30–16:00 Tamansari β€” pemandian Sultan + sumur Gumuling +β€’ 16:00–17:30 Coffee break di kedai lokal Prawirotaman +β€’ 17:30–19:30 Sunset di Bukit Bintang (Jl. Imogiri) +β€’ 19:30–21:30 Angkringan Lik Man β€” kopi joss + nasi kucing +β€’ 21:30 Drop ke penginapan masing-masing + +Hari 2 β€” Minggu +β€’ 06:00–07:00 Pickup dari penginapan +β€’ 07:00–09:30 Perjalanan ke Kalibiru, Kulon Progo +β€’ 09:30–11:30 Kalibiru β€” spot foto rumah pohon di tebing +β€’ 11:30–13:00 Makan siang pecel di warung lokal +β€’ 13:00–15:00 Pinus Pengger β€” instalasi seni alam +β€’ 15:00–16:30 Heha Sky View (opsional, cek cuaca) +β€’ 16:30–18:00 Kembali ke kota β€” drop di Stasiun Tugu / Bandara +β€’ 18:00 Bubar grup`; + +const ITIN_CULINARY = `Hari 1 β€” Sabtu (one-day food tour) +β€’ 09:00–09:30 Meeting di Stasiun Bandung pintu utara + briefing rute +β€’ 09:30–10:15 Stop 1: Surabi Enhaii (sarapan tradisional) +β€’ 10:15–11:00 Stop 2: Lotek Kalipah Apo +β€’ 11:00–11:45 Stop 3: Mie Kocok Mang Dadeng (Kebon Jukut) +β€’ 11:45–12:30 Stop 4: Bakso Akung (cabang Burangrang) +β€’ 12:30–13:30 Istirahat + jalan santai di Cihampelas +β€’ 13:30–14:15 Stop 5: Batagor Kingsley +β€’ 14:15–15:00 Stop 6: Cuanki Serayu +β€’ 15:00–15:45 Stop 7: Es Cendol Elizabeth +β€’ 15:45–16:30 Stop 8: Roti Gempol & Kopi Anjis (penutup) +β€’ 16:30–17:00 Closing + foto bareng di Braga`; + +const ITIN_CONCERT = `Hari 1 β€” Sabtu (showtime) +β€’ 17:00–17:30 Meeting di Plaza GBK, depan loket Cat 1 +β€’ 17:30–18:30 Foto bareng pre-show + obrolan singkat +β€’ 18:30–19:00 Masuk venue bareng (kategori tetap masing-masing) +β€’ 19:00–22:30 Konser Coldplay β€” Music of the Spheres +β€’ 22:30–23:00 Berkumpul lagi di luar gerbang utama GBK +β€’ 23:00–00:30 After-party dinner di Senayan (resto TBA via grup WA) +β€’ 00:30 Bubar`; + +const ITIN_WORKSHOP = `Hari 1 β€” Sabtu +β€’ 04:00–04:30 Meeting di Alun-alun Pangalengan +β€’ 04:30–05:30 Briefing teknis + setup peralatan +β€’ 05:30–07:30 Sunrise shoot di Perkebunan Teh Malabar +β€’ 07:30–09:00 Sarapan + sesi review foto bareng mentor +β€’ 09:00–11:00 Materi indoor: long exposure & filter ND +β€’ 11:00–13:00 ISHOMA + transfer ke Situ Cileunca +β€’ 13:00–16:00 Workshop on-field di Situ Cileunca (panorama, refleksi) +β€’ 16:00–18:00 Golden hour shoot di Bukit Nini +β€’ 18:00–19:30 Makan malam di villa +β€’ 19:30–22:00 Sesi malam β€” milky way / star trail (cuaca permitting) +β€’ 22:00 Istirahat di villa + +Hari 2 β€” Minggu +β€’ 05:00–07:00 Sunrise shoot di Pangalengan + foto siluet +β€’ 07:00–08:30 Sarapan + diskusi hasil +β€’ 08:30–11:00 Sesi editing Lightroom (laptop pribadi) +β€’ 11:00–12:00 Review akhir + sertifikat +β€’ 12:00–13:00 Makan siang penutupan +β€’ 13:00–15:00 Kembali ke Alun-alun Pangalengan +β€’ 15:00 Bubar grup`; + +const ITIN_RETREAT = `Hari 1 β€” Jumat +β€’ 14:00–15:00 Check-in Villa Sawah Ubud + welcome drink (jamu) +β€’ 15:00–16:00 Tour fasilitas + pembagian welcome kit +β€’ 16:00–17:30 Yin Yoga pembuka β€” release perjalanan +β€’ 17:30–18:30 Journaling: niat & ekspektasi retreat +β€’ 18:30–20:00 Dinner vegan (set menu) +β€’ 20:00–21:00 Circle pengenalan + meditasi singkat +β€’ 21:00 Lights-out + +Hari 2 β€” Sabtu +β€’ 06:00–07:30 Hatha Yoga matahari terbit +β€’ 07:30–09:00 Sarapan vegan + tea ceremony +β€’ 09:00–10:30 Meditasi guided: body scan +β€’ 10:30–12:00 Pranayama (latihan napas) +β€’ 12:00–13:30 Lunch + acara bebas (sawah walk) +β€’ 13:30–15:00 Sound healing dengan singing bowl +β€’ 15:00–16:30 Workshop: mindful eating + jamu making +β€’ 16:30–18:00 Yin Yoga sore + savasana panjang +β€’ 18:00–19:30 Dinner vegan +β€’ 19:30–21:00 Sharing circle + meditasi malam +β€’ 21:00 Lights-out + +Hari 3 β€” Minggu +β€’ 06:00–07:30 Vinyasa Flow penutupan +β€’ 07:30–09:00 Sarapan + closing journaling +β€’ 09:00–10:30 Closing circle + tukar pesan +β€’ 10:30–11:30 Check-out + pelukan perpisahan +β€’ 11:30–14:00 Acara bebas (rekomendasi spa/cafe sekitar) +β€’ 14:00 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() { + await prisma.payment.deleteMany(); + await prisma.booking.deleteMany(); await prisma.tripReview.deleteMany(); - await prisma.tripParticipant.deleteMany(); await prisma.tripImage.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(); +} - // ==================== USERS ==================== +// ============================================================================ +// 2. Users + Profile +// ============================================================================ - const password = await bcrypt.hash("password123", 12); +interface SeedUser { + key: string; + name: string; + email: string; + isAdmin?: boolean; + profile?: { + bio?: string; + city?: string; + interests?: string[]; + instagram?: string; + vibe?: Vibe; + }; +} - 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 }, - }); +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", + }, + }, +]; - 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"); +type UserMap = Record; - // ==================== ORGANIZER VERIFICATIONS ==================== +async function seedUsers(): Promise { + const password = await bcrypt.hash(SEED_PASSWORD, 12); + const acceptedAt = new Date(); - 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, + 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, }, - { - 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, + }); + 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; + itinerary?: string; + 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", + itinerary: 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" }, ], - }); - console.log("βœ… OrganizerVerification (APPROVED) untuk Dede & Panji\n"); + }, + { + 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", + itinerary: 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", + itinerary: 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: [], + }, - // ==================== 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! + // ============ 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.", - 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 + meetingPoint: "Alun-alun Garut, Sabtu 05:00 WIB β€” detail grup WA.", + itinerary: ITIN_PAPANDAYAN, + whatsIncluded: `β€’ Transport PP Garut–basecamp β€’ Guide lokal β€’ Tenda tim β€’ Konsumsi 3x + snack`, - whatsExcluded: `β€’ Tiket masuk TN + 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! πŸ”οΈ + 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`, - 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. + meetingPoint: "Stasiun Kuningan, Sabtu 04:00 WIB", + itinerary: 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", - whatsIncluded: `β€’ Tenda + sleeping bag + matras + meetingPoint: "Pertigaan Pasar Lembang, Sabtu 13:00 WIB", + itinerary: ITIN_CAMPING, + whatsIncluded: `β€’ Tenda + sleeping bag + matras β€’ Logistik camp β€’ Makan malam BBQ + sarapan β€’ Tiket masuk lokasi`, - whatsExcluded: `β€’ Transport pribadi + 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. + 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", - whatsIncluded: `β€’ Boat PP + meetingPoint: "Dermaga Ketapang, Lampung Selatan, 07:00 WIB", + itinerary: ITIN_PAHAWANG, + whatsIncluded: `β€’ Boat PP β€’ Alat snorkel (masker, fin, life vest) β€’ Guide & pemandu underwater β€’ Konsumsi 2x`, - whatsExcluded: `β€’ Transport ke Lampung + 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. + 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", - whatsIncluded: `β€’ 2x dive guided + meetingPoint: "Dive shop Tulamben, 06:30 WITA", + itinerary: ITIN_DIVING, + whatsIncluded: `β€’ 2x dive guided β€’ Full gear rental β€’ Tank & weight β€’ Konsumsi siang`, - whatsExcluded: `β€’ Transport ke Bali + 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. + 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", - whatsIncluded: `β€’ Tiket kapal feri PP Jepara–Karimun + meetingPoint: "Pelabuhan Kartini Jepara, Jumat 07:00 WIB", + itinerary: 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 + 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. + 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", - whatsIncluded: `β€’ Transport mobil grup 2 hari + meetingPoint: "Stasiun Tugu Yogyakarta, Sabtu 08:00 WIB", + itinerary: ITIN_CITYTRIP, + whatsIncluded: `β€’ Transport mobil grup 2 hari β€’ Tour leader lokal β€’ Makan 3x (kuliner lokal) β€’ Tiket masuk semua spot`, - whatsExcluded: `β€’ Transport ke Jogja + 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. + 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", - whatsIncluded: `β€’ Transport angkot/grup + meetingPoint: "Stasiun Bandung pintu utara, 09:00 WIB", + itinerary: 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, - 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. + 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", - whatsIncluded: `β€’ Koordinasi grup + meetingPoint: "Plaza GBK, depan loket Cat 1, 17:00 WIB", + itinerary: ITIN_CONCERT, + whatsIncluded: `β€’ Koordinasi grup β€’ Foto bareng pre-show β€’ After-party dinner di Senayan`, - whatsExcluded: `β€’ Tiket konser (bawa sendiri!) + 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). + 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", - whatsIncluded: `β€’ Materi workshop (briefing + on-field) + meetingPoint: "Alun-alun Pangalengan, Sabtu 04:00 WIB", + itinerary: ITIN_WORKSHOP, + whatsIncluded: `β€’ Materi workshop (briefing + on-field) β€’ Tour leader & mentor β€’ Penginapan villa 1 malam β€’ Konsumsi 3x`, - whatsExcluded: `β€’ Kamera & tripod (bawa sendiri) + 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. + 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)", - whatsIncluded: `β€’ Penginapan villa 2 malam + meetingPoint: "Villa Sawah Ubud (alamat dikirim H-3 via WA)", + itinerary: 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 + 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" }, + 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" }, ], - }); + }, +]; - console.log("βœ… Participants joined (mix CONFIRMED & PENDING per trip)\n"); - console.log("πŸŽ‰ Seed complete!"); +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, + itinerary: t.itinerary, + 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, + }, + }); + 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()