Files
2026-05-20 16:52:54 +07:00

2102 lines
80 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
// ============================================================================
/** Tanggal absolut UTC — dipakai untuk data yang tidak boleh bergeser (tanggal lahir). */
const utc = (y: number, m0: number, d: number, h = 12, min = 0) =>
new Date(Date.UTC(y, m0, d, h, min, 0, 0));
/** Waktu seed dijalankan — basis semua tanggal relatif. */
const SEED_NOW = new Date();
/**
* Tanggal relatif terhadap waktu seed dijalankan — bikin seeder *evergreen*:
* kapan pun di-run, trip "OPEN" selalu di masa depan dan "COMPLETED" di masa
* lalu. `offsetDays` negatif = lampau, positif = mendatang. Hasil = tengah
* malam UTC, sinkron dengan trip yang dibuat lewat form (`tripStoredInstantFromYmd`).
*/
function relDate(offsetDays: number): Date {
const d = new Date(SEED_NOW);
d.setUTCDate(d.getUTCDate() + offsetDays);
d.setUTCHours(0, 0, 0, 0);
return d;
}
const img = (id: string) =>
`https://images.unsplash.com/photo-${id}?w=1200&q=80&auto=format&fit=crop`;
/**
* Kumpulan foto Unsplash per kategori — di-reuse antar trip supaya tidak ada
* URL gambar yang 404. Semua id sudah terverifikasi valid.
*/
const PHOTO = {
hiking: [
"1554629947-334ff61d85dc",
"1464822759023-fed622ff2c3b",
"1480497490787-505ec076689f",
"1502085671122-2d218cd434e6",
],
camping: ["1504280390367-361c6d9f38f4", "1517824806704-9040b037703b"],
beach: [
"1583212292454-1fe6229603b7",
"1559825481-12a05cc00344",
"1507525428034-b723cf961d3e",
"1519046904884-53103b34b206",
],
diving: ["1544551763-46a013bb70d5", "1566024287286-457247b70310"],
city: ["1596402184320-417e7178b2cd", "1583309217394-d191d747bc66"],
culinary: ["1565299624946-b28f40a0ae38"],
concert: ["1470229722913-7c0e2dbbafd3"],
workshop: ["1452587925148-ce544e77e70d", "1444080748397-f442aa95c3e5"],
retreat: ["1545389336-cf090694435e", "1518609878373-06d740f60d8b"],
} as const;
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 (528m, ~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" },
];
const ITIN_PRAU: SeedItineraryItem[] = [
{ day: 1, startTime: "13:00", endTime: "13:30", activity: "Meeting & registrasi di basecamp Patak Banteng, Dieng" },
{ day: 1, startTime: "13:30", endTime: "14:00", activity: "Briefing + repacking + pemanasan" },
{ day: 1, startTime: "14:00", endTime: "17:00", activity: "Trekking Pos 1 — Pos 2 — Pos 3 menuju area camp puncak" },
{ day: 1, startTime: "17:00", endTime: "18:30", activity: "Setup tenda + makan sore + sunset" },
{ day: 1, startTime: "18:30", endTime: "21:00", activity: "Api unggun, kopi, sharing antar peserta" },
{ day: 1, startTime: "21:00", activity: "Istirahat" },
{ day: 2, startTime: "04:30", endTime: "05:00", activity: "Bangun + jalan ke spot sunrise" },
{ day: 2, startTime: "05:00", endTime: "06:30", activity: "Golden sunrise + Bukit Teletubbies + foto bareng" },
{ day: 2, startTime: "06:30", endTime: "08:30", activity: "Kembali ke camp + sarapan" },
{ day: 2, startTime: "08:30", endTime: "10:00", activity: "Beres tenda + repacking" },
{ day: 2, startTime: "10:00", endTime: "12:00", activity: "Turun ke basecamp Patak Banteng" },
{ day: 2, startTime: "12:00", activity: "Bersih-bersih, bubar grup" },
];
const ITIN_BROMO: SeedItineraryItem[] = [
{ day: 1, startTime: "00:00", endTime: "00:30", activity: "Meeting di Cemoro Lawang + briefing" },
{ day: 1, startTime: "00:30", endTime: "03:00", activity: "Jeep menuju Penanjakan + posisi spot sunrise" },
{ day: 1, startTime: "03:00", endTime: "05:30", activity: "Menunggu + sunrise di view point Penanjakan" },
{ day: 1, startTime: "05:30", endTime: "07:00", activity: "Jeep ke lautan pasir + Pura Luhur Poten" },
{ day: 1, startTime: "07:00", endTime: "08:30", activity: "Trek tangga ke kawah Bromo" },
{ day: 1, startTime: "08:30", endTime: "10:00", activity: "Bukit Teletubbies + Pasir Berbisik (sesi foto)" },
{ day: 1, startTime: "10:00", endTime: "11:00", activity: "Sarapan + bersih-bersih" },
{ day: 1, startTime: "11:00", activity: "Bubar grup" },
];
const ITIN_NUSAPENIDA: SeedItineraryItem[] = [
{ day: 1, startTime: "06:00", endTime: "06:30", activity: "Meeting di Sanur, Bali + briefing" },
{ day: 1, startTime: "06:30", endTime: "07:30", activity: "Fast boat Sanur menuju Nusa Penida" },
{ day: 1, startTime: "07:30", endTime: "09:30", activity: "Kelingking Beach — spot ikonik tebing T-Rex" },
{ day: 1, startTime: "09:30", endTime: "11:00", activity: "Angel's Billabong + Broken Beach" },
{ day: 1, startTime: "11:00", endTime: "12:30", activity: "Makan siang + istirahat" },
{ day: 1, startTime: "12:30", endTime: "14:30", activity: "Crystal Bay — snorkeling + santai pantai" },
{ day: 1, startTime: "14:30", endTime: "15:30", activity: "Fast boat kembali ke Sanur" },
{ day: 1, startTime: "15:30", activity: "Tiba di Sanur, bubar grup" },
];
const ITIN_RINJANI: SeedItineraryItem[] = [
{ day: 1, startTime: "06:00", endTime: "07:00", activity: "Meeting & registrasi di basecamp Sembalun" },
{ day: 1, startTime: "07:00", endTime: "08:00", activity: "Sarapan + briefing + repacking bareng porter" },
{ day: 1, startTime: "08:00", endTime: "13:00", activity: "Trek Sembalun → Pos 1 → Pos 2 (sabana luas)" },
{ day: 1, startTime: "13:00", endTime: "14:00", activity: "ISHOMA di Pos 2" },
{ day: 1, startTime: "14:00", endTime: "17:00", activity: "Lanjut ke Pos 3 — Plawangan Sembalun" },
{ day: 1, startTime: "17:00", endTime: "19:00", activity: "Setup tenda + makan malam + sunset" },
{ day: 1, startTime: "19:00", activity: "Istirahat (summit dini hari)" },
{ day: 2, startTime: "02:00", endTime: "02:30", activity: "Bangun + minuman hangat + briefing summit" },
{ day: 2, startTime: "02:30", endTime: "06:00", activity: "Summit attack ke puncak Rinjani (3.726 mdpl)" },
{ day: 2, startTime: "06:00", endTime: "07:30", activity: "Sunrise di puncak + foto bareng" },
{ day: 2, startTime: "07:30", endTime: "10:30", activity: "Turun ke Plawangan + sarapan" },
{ day: 2, startTime: "10:30", endTime: "14:30", activity: "Turun ke Danau Segara Anak" },
{ day: 2, startTime: "14:30", endTime: "17:00", activity: "Camp di tepi danau + berendam air panas" },
{ day: 2, startTime: "17:00", endTime: "19:00", activity: "Makan malam + santai di tepi danau" },
{ day: 2, startTime: "19:00", activity: "Istirahat" },
{ day: 3, startTime: "06:00", endTime: "07:30", activity: "Sarapan + beres tenda" },
{ day: 3, startTime: "07:30", endTime: "13:00", activity: "Trek turun via Senaru (hutan tropis)" },
{ day: 3, startTime: "13:00", endTime: "14:00", activity: "Makan siang di basecamp Senaru" },
{ day: 3, startTime: "14:00", activity: "Bersih-bersih, bubar grup" },
];
// ============================================================================
// 1. Cleanup — hapus SEMUA data lama supaya tiap `npm run seed` mulai dari nol.
// Urutan FK-aware: Refund/Payout → Payment → Booking → Review → Image →
// Itinerary → Participant → Trip → OrgVerification → Profile → Account →
// User. Tabel log operasional (email/cron/audit) juga dibersihkan.
// ============================================================================
async function cleanup() {
// Log operasional — tidak punya FK ke data inti, aman dihapus duluan.
await prisma.emailJob.deleteMany();
await prisma.emailSent.deleteMany();
await prisma.cronRun.deleteMany();
await prisma.adminActionLog.deleteMany();
// 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",
},
},
{
key: "rangga",
name: "Rangga Wisesa",
email: "rangga@setrip.id",
profile: {
bio: "Founder komunitas trip Jawa Timur. Hobi sunrise hunting & curug.",
city: "Malang",
interests: ["hiking", "camping", "fotografi"],
instagram: "rangga.wisesa",
vibe: "HARDCORE",
},
},
{
key: "ayu",
name: "Ayu Lestari",
email: "ayu@setrip.id",
profile: {
bio: "Open trip organizer Bali & NTT. Cinta laut, anti drama.",
city: "Denpasar",
interests: ["diving", "snorkeling", "island hopping"],
instagram: "ayu.lestari",
vibe: "BALANCED",
},
},
// 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",
},
},
{
key: "intan",
name: "Intan Permata",
email: "intan@gmail.com",
profile: {
bio: "Pencinta sunrise & kopi gunung. Lagi ngumpulin teman seperjalanan.",
city: "Bandung",
interests: ["hiking", "fotografi", "kuliner"],
instagram: "intan.permata",
vibe: "BALANCED",
},
},
{
key: "galih",
name: "Galih Nugraha",
email: "galih@gmail.com",
profile: {
bio: "Weekend hiker dari Semarang. Suka camping & ngobrol sampai pagi.",
city: "Semarang",
interests: ["hiking", "camping"],
vibe: "BALANCED",
},
},
{
key: "wina",
name: "Wina Oktaviani",
email: "wina@gmail.com",
profile: {
bio: "Beach person. Kerja remote, jalan tiap ada libur panjang.",
city: "Jakarta",
interests: ["snorkeling", "island hopping", "fotografi"],
instagram: "wina.okta",
vibe: "CHILL",
},
},
{
key: "bayu",
name: "Bayu Saputra",
email: "bayu@gmail.com",
profile: {
bio: "Hobi naik gunung sejak kuliah. Target: 7 summit Jawa.",
city: "Malang",
interests: ["hiking", "diving"],
vibe: "HARDCORE",
},
},
];
type UserMap = Record<string, { id: string; email: string; name: string }>;
async function seedUsers(): Promise<UserMap> {
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",
},
{
userKey: "rangga",
fullName: "Rangga Wisesa",
nik: "3573010101010004",
birthDate: utc(1991, 7, 20, 0, 0),
address: "Jl. Ijen No. 23, Malang, Jawa Timur",
bankName: "BRI",
bankAccountNumber: "3344556677",
bankAccountName: "Rangga Wisesa",
status: "APPROVED",
},
{
userKey: "ayu",
fullName: "Ayu Lestari",
nik: "5171010101010005",
birthDate: utc(1993, 10, 8, 0, 0),
address: "Jl. Danau Tamblingan No. 5, Denpasar, Bali",
bankName: "BCA",
bankAccountNumber: "7788990011",
bankAccountName: "Ayu Lestari",
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 — semua tanggal RELATIF terhadap waktu seed (lihat `relDate`):
// COMPLETED/CLOSED < hari ini, OPEN/FULL > hari ini. Referensi destinasi
// & kisaran harga diambil dari listing open trip populer di Indonesia.
// ============================================================================
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[];
}
/** Bangun daftar gambar dari pool kategori. */
function pics(
pool: readonly string[],
captions: string[]
): SeedTripImage[] {
return captions.map((caption, i) => ({
url: img(pool[i % pool.length]),
caption,
}));
}
const SEED_TRIPS: SeedTrip[] = [
// ============ MASA LALU (COMPLETED — ada review) ============
{
key: "past_papandayan",
organizerKey: "dede",
category: "HIKING",
title: "Open Trip Papandayan — Batch Lalu",
description: `Pendakian santai ke Gunung Papandayan. Cocok untuk pemula.`,
meetingPoint: "Alun-alun Garut, 05:00 WIB",
itineraryItems: ITIN_PAPANDAYAN,
destination: "Gunung Papandayan",
location: "Garut, Jawa Barat",
date: relDate(-67),
endDate: relDate(-66),
maxParticipants: 10,
price: 250000,
vibe: "BALANCED",
status: "COMPLETED",
images: pics(PHOTO.hiking, ["Kawah Papandayan", "Track menuju puncak"]),
},
{
key: "past_pahawang",
organizerKey: "panji",
category: "SNORKELING",
title: "Snorkeling Pulau Pahawang — Batch Lalu",
description: `Trip snorkeling batch lalu. Cuaca bersahabat, visibility 10m+.`,
meetingPoint: "Dermaga Ketapang, Lampung Selatan, 07:00 WIB",
itineraryItems: ITIN_PAHAWANG,
destination: "Pulau Pahawang Kecil",
location: "Lampung Selatan, Lampung",
date: relDate(-88),
endDate: null,
maxParticipants: 10,
price: 380000,
vibe: "CHILL",
status: "COMPLETED",
images: pics(PHOTO.beach, ["Snorkeling di Pahawang"]),
},
{
key: "past_culinary",
organizerKey: "dede",
category: "CULINARY",
title: "Kulineran Street Food Bandung — Batch Lalu",
description: `Food tour batch lalu. 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: relDate(-38),
endDate: null,
maxParticipants: 8,
price: 175000,
vibe: "CHILL",
status: "COMPLETED",
images: pics(PHOTO.culinary, ["Street food Bandung"]),
},
{
key: "past_prau",
organizerKey: "rangga",
category: "HIKING",
title: "Open Trip Gunung Prau Dieng — Batch Lalu",
description: `Sunrise camp di Prau, batch lalu. Cuaca cerah, lautan awan sempurna.`,
meetingPoint: "Basecamp Patak Banteng, Dieng, 13:00 WIB",
itineraryItems: ITIN_PRAU,
destination: "Gunung Prau",
location: "Wonosobo, Jawa Tengah",
date: relDate(-25),
endDate: relDate(-24),
maxParticipants: 15,
price: 320000,
vibe: "BALANCED",
status: "COMPLETED",
images: pics(PHOTO.hiking, ["Sunrise Gunung Prau", "Bukit Teletubbies"]),
},
{
key: "past_nusapenida",
organizerKey: "ayu",
category: "ISLAND_HOPPING",
title: "Nusa Penida West Tour — Batch Lalu",
description: `One day tour Nusa Penida sisi barat. Kelingking, Crystal Bay, dan kawan-kawan.`,
meetingPoint: "Pantai Sanur, Bali, 06:00 WITA",
itineraryItems: ITIN_NUSAPENIDA,
destination: "Nusa Penida Barat",
location: "Klungkung, Bali",
date: relDate(-45),
endDate: null,
maxParticipants: 14,
price: 600000,
vibe: "BALANCED",
status: "COMPLETED",
images: pics(PHOTO.beach, ["Kelingking Beach", "Crystal Bay"]),
},
// ============ TRIP DIBATALKAN (CLOSED) ============
{
key: "past_workshop_cancelled",
organizerKey: "panji",
category: "WORKSHOP",
title: "Workshop Astrofotografi Bromo (BATAL)",
description: `Cuaca tidak mendukung. Trip dibatalkan H-3, peserta refund 100%.`,
meetingPoint: "-",
destination: "Astrofotografi Bromo",
location: "Probolinggo, Jawa Timur",
date: relDate(-110),
endDate: relDate(-109),
maxParticipants: 6,
price: 950000,
vibe: "HARDCORE",
status: "CLOSED",
images: [],
},
// ============ MASA DEPAN (OPEN — fitur join + bayar bisa di-test) ============
{
key: "open_kotatua",
organizerKey: "fiersa",
category: "CITY_TRIP",
title: "Heritage Walk Kota Tua Jakarta",
description: `Jalan kaki menyusuri Batavia lama bareng pemandu sejarah.
🚶 Santai, ramah pemula — cocok buat after-office Sabtu`,
meetingPoint: "Stasiun Jakarta Kota (BEOS), Sabtu 07:00 WIB",
whatsIncluded: `• Pemandu sejarah lokal
• Tiket Museum Fatahillah
• Snack + air mineral`,
whatsExcluded: `• Transport ke Kota Tua
• Makan siang`,
destination: "Kota Tua Jakarta",
location: "Jakarta Barat",
date: relDate(6),
endDate: null,
maxParticipants: 15,
price: 95000,
vibe: "CHILL",
status: "OPEN",
images: pics(PHOTO.city, ["Museum Fatahillah", "Sudut Kota Tua"]),
},
{
key: "open_prau",
organizerKey: "rangga",
category: "HIKING",
title: "Open Trip Gunung Prau via Patak Banteng",
description: `Golden sunrise terbaik se-Jawa Tengah. 🌄
⛰️ Track pendek 23 jam — paling ramah pemula
🎒 Bawa: jaket tebal, sleeping bag, headlamp`,
meetingPoint: "Basecamp Patak Banteng, Dieng, Sabtu 13:00 WIB",
itineraryItems: ITIN_PRAU,
whatsIncluded: `• Tenda + matras tim
• Guide lokal
• Konsumsi 3x + air panas
• Tiket masuk + simaksi`,
whatsExcluded: `• Transport ke Dieng
• Sleeping bag pribadi`,
destination: "Gunung Prau",
location: "Wonosobo, Jawa Tengah",
date: relDate(7),
endDate: relDate(8),
maxParticipants: 15,
price: 320000,
vibe: "BALANCED",
status: "OPEN",
images: pics(PHOTO.hiking, ["Sunrise Gunung Prau", "Camp area puncak"]),
},
{
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: relDate(8),
endDate: null,
maxParticipants: 8,
price: 175000,
vibe: "CHILL",
status: "OPEN",
images: pics(PHOTO.culinary, ["Street food Bandung"]),
},
{
key: "open_seribu",
organizerKey: "panji",
category: "SNORKELING",
title: "Open Trip Pulau Harapan 2D1N",
description: `Snorkeling hopping ala Maladewa-nya Jakarta. 🏝️
✅ Trip ini sudah PENUH — pantau jadwal batch berikutnya`,
meetingPoint: "Pelabuhan Kaliadem Muara Angke, Sabtu 06:00 WIB",
whatsIncluded: `• Kapal PP + boat hopping
• Homestay 1 malam
• Alat snorkel
• Makan 4x + BBQ`,
whatsExcluded: `• Transport ke Muara Angke
• Sewa kamera underwater`,
destination: "Pulau Harapan",
location: "Kepulauan Seribu, DKI Jakarta",
date: relDate(9),
endDate: relDate(10),
maxParticipants: 6,
price: 475000,
vibe: "CHILL",
status: "FULL",
images: pics(PHOTO.beach, ["Pasir putih Pulau Harapan", "Spot snorkeling"]),
},
{
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 Garutbasecamp
• Guide lokal
• Tenda tim
• Konsumsi 3x + snack`,
whatsExcluded: `• Tiket masuk TN
• Sleeping bag pribadi
• Asuransi perjalanan`,
destination: "Gunung Papandayan",
location: "Garut, Jawa Barat",
date: relDate(10),
endDate: relDate(11),
maxParticipants: 10,
price: 250000,
vibe: "BALANCED",
status: "OPEN",
images: pics(PHOTO.hiking, ["Kawah Papandayan", "Track menuju puncak"]),
},
{
key: "open_bromo",
organizerKey: "rangga",
category: "HIKING",
title: "Bromo Sunrise Midnight Trip",
description: `Berburu sunrise legendaris Bromo dari Penanjakan. 🌅
🚙 Naik jeep 4x4 — gak perlu trekking berat
📷 Spot foto: kawah, lautan pasir, Bukit Teletubbies`,
meetingPoint: "Cemoro Lawang, Probolinggo, 00:00 WIB",
itineraryItems: ITIN_BROMO,
whatsIncluded: `• Jeep wisata 4x4
• Guide lokal
• Tiket masuk kawasan TNBTS
• Sarapan ringan`,
whatsExcluded: `• Transport ke Cemoro Lawang
• Sewa kuda ke kawah`,
destination: "Gunung Bromo",
location: "Probolinggo, Jawa Timur",
date: relDate(12),
endDate: null,
maxParticipants: 12,
price: 475000,
vibe: "BALANCED",
status: "OPEN",
images: pics(PHOTO.hiking, ["Sunrise Bromo", "Lautan pasir"]),
},
{
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: relDate(13),
endDate: relDate(14),
maxParticipants: 12,
price: 220000,
vibe: "CHILL",
status: "OPEN",
images: pics(PHOTO.camping, ["Tenda di hutan pinus", "Sunset di camp"]),
},
{
key: "open_semarang_kuliner",
organizerKey: "rangga",
category: "CULINARY",
title: "Kulineran Heritage Semarang",
description: `Susur kuliner legendaris Semarang sambil jalan di Kota Lama.
🥟 Lumpia, tahu gimbal, nasi ayam, sampai es puter`,
meetingPoint: "Stasiun Semarang Tawang, 09:00 WIB",
whatsIncluded: `• Tour leader food explorer
• Sample 7 spot kuliner
• Air mineral`,
whatsExcluded: `• Transport ke Semarang
• Pembelian di luar sample`,
destination: "Kuliner Kota Lama Semarang",
location: "Semarang, Jawa Tengah",
date: relDate(15),
endDate: null,
maxParticipants: 10,
price: 185000,
vibe: "CHILL",
status: "OPEN",
images: pics(PHOTO.culinary, ["Kuliner Semarang"]),
},
{
key: "open_nusapenida",
organizerKey: "ayu",
category: "ISLAND_HOPPING",
title: "Nusa Penida West One Day Trip",
description: `Eksplor spot ikonik Nusa Penida sisi barat dalam sehari.
🏝️ Kelingking, Angel's Billabong, Broken Beach, Crystal Bay`,
meetingPoint: "Pantai Sanur, Bali, 06:00 WITA",
itineraryItems: ITIN_NUSAPENIDA,
whatsIncluded: `• Fast boat PP SanurNusa Penida
• Mobil + sopir lokal seharian
• Makan siang
• Tiket masuk semua spot`,
whatsExcluded: `• Transport ke Sanur
• Alat snorkel (sewa di lokasi)`,
destination: "Nusa Penida Barat",
location: "Klungkung, Bali",
date: relDate(16),
endDate: null,
maxParticipants: 14,
price: 625000,
vibe: "BALANCED",
status: "OPEN",
images: pics(PHOTO.beach, ["Kelingking Beach", "Broken Beach"]),
},
{
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: relDate(18),
endDate: relDate(19),
maxParticipants: 8,
price: 650000,
vibe: "BALANCED",
status: "OPEN",
images: pics(PHOTO.city, ["Tamansari", "Sudut Jogja"]),
},
{
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,
whatsIncluded: `• Transport PP Kuninganbasecamp
• Guide + porter logistik
• Tenda tim + konsumsi 4x
• Simaksi`,
whatsExcluded: `• Sleeping bag pribadi
• Asuransi perjalanan`,
destination: "Gunung Ciremai",
location: "Kuningan, Jawa Barat",
date: relDate(20),
endDate: relDate(21),
maxParticipants: 8,
price: 350000,
vibe: "HARDCORE",
status: "OPEN",
images: pics(PHOTO.hiking, ["Puncak Ciremai 3.078 mdpl", "Sunrise dari puncak"]),
},
{
key: "open_sentul_camp",
organizerKey: "rangga",
category: "CAMPING",
title: "Camping & Curug Hunting Sentul",
description: `Camping di tepi sungai + main air di curug-curug Sentul.
💧 Leuwi Hejo, Leuwi Lieuk — air jernih kebiruan`,
meetingPoint: "Pertigaan Sentul City, Bogor, Sabtu 09:00 WIB",
whatsIncluded: `• Tenda + matras + sleeping bag
• Guide curug lokal
• Makan 3x + BBQ malam
• Tiket masuk semua curug`,
whatsExcluded: `• Transport ke Sentul
• Sandal gunung pribadi`,
destination: "Curug Leuwi Hejo",
location: "Bogor, Jawa Barat",
date: relDate(22),
endDate: relDate(23),
maxParticipants: 14,
price: 265000,
vibe: "CHILL",
status: "OPEN",
images: pics(PHOTO.camping, ["Camp tepi sungai", "Curug Leuwi Hejo"]),
},
{
key: "open_merbabu",
organizerKey: "panji",
category: "HIKING",
title: "Pendakian Merbabu via Suwanting",
description: `Sabana Merbabu yang ikonik — salah satu jalur terindah di Jawa. 🌾
⚠️ Level: Menengahlanjut, jalur panjang & menanjak`,
meetingPoint: "Basecamp Suwanting, Magelang, Sabtu 05:00 WIB",
whatsIncluded: `• Guide + porter logistik
• Tenda tim + konsumsi 4x
• Simaksi + tiket masuk`,
whatsExcluded: `• Transport ke Magelang
• Sleeping bag & jaket pribadi`,
destination: "Gunung Merbabu",
location: "Magelang, Jawa Tengah",
date: relDate(24),
endDate: relDate(25),
maxParticipants: 10,
price: 375000,
vibe: "HARDCORE",
status: "OPEN",
images: pics(PHOTO.hiking, ["Sabana Merbabu", "Jalur Suwanting"]),
},
{
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: relDate(27),
endDate: null,
maxParticipants: 10,
price: 380000,
vibe: "CHILL",
status: "OPEN",
images: pics(PHOTO.beach, ["Snorkeling di Pahawang", "Pasir putih Pahawang"]),
},
{
key: "open_lembongan_dive",
organizerKey: "ayu",
category: "DIVING",
title: "Fun Dive Nusa Lembongan — Manta Point",
description: `Diving bareng manta ray raksasa di Manta Bay! 🐠
⚠️ Sertifikasi minimal: Open Water (PADI/SSI)
🌊 Arus sedang — bukan untuk dive pertama`,
meetingPoint: "Dive center Sanur, Bali, 07:00 WITA",
whatsIncluded: `• 2x dive guided (Manta Point + Crystal Bay)
• Full gear + tank & weight
• Fast boat PP
• Makan siang`,
whatsExcluded: `• Transport ke Sanur
• Sertifikasi (cek validitas)`,
destination: "Manta Point Nusa Lembongan",
location: "Klungkung, Bali",
date: relDate(29),
endDate: relDate(30),
maxParticipants: 6,
price: 1550000,
vibe: "HARDCORE",
status: "OPEN",
images: pics(PHOTO.diving, ["Manta ray", "Reef Nusa Lembongan"]),
},
{
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: relDate(30),
endDate: relDate(31),
maxParticipants: 6,
price: 850000,
vibe: "BALANCED",
status: "OPEN",
images: pics(PHOTO.workshop, ["Sunrise kebun teh", "Workshop on-field"]),
},
{
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: relDate(33),
endDate: relDate(34),
maxParticipants: 6,
price: 1850000,
vibe: "HARDCORE",
status: "OPEN",
images: pics(PHOTO.diving, ["Wreck dive Tulamben", "Reef Tulamben"]),
},
{
key: "open_festival",
organizerKey: "fiersa",
category: "CONCERT",
title: "Nonton Festival Musik Bareng — Pestapora",
description: `Cari rombongan buat nonton festival musik biar gak sendirian?
🎫 Tiket BUKAN termasuk — peserta bawa masing-masing
🤝 Grup untuk koordinasi meet-up, line-up favorit & pulang bareng`,
meetingPoint: "Gate utama venue, 15:00 WIB",
whatsIncluded: `• Koordinasi grup + grup WA
• Foto bareng
• Atur jadwal nonton line-up favorit`,
whatsExcluded: `• Tiket festival (bawa sendiri!)
• Transport ke venue`,
destination: "Festival Musik Pestapora",
location: "Jakarta",
date: relDate(33),
endDate: null,
maxParticipants: 8,
price: 0,
vibe: "BALANCED",
status: "OPEN",
images: pics(PHOTO.concert, ["Festival musik"]),
},
{
key: "open_rinjani",
organizerKey: "ayu",
category: "HIKING",
title: "Pendakian Rinjani Summit 3D2N via Sembalun",
description: `Summit Rinjani 3.726 mdpl + camp di tepi Danau Segara Anak. 🏔️
⚠️ Level: LANJUT — wajib fisik prima & pengalaman gunung
🔥 Salah satu pendakian terbaik di Indonesia`,
meetingPoint: "Basecamp Sembalun, Lombok Timur, 06:00 WITA",
itineraryItems: ITIN_RINJANI,
whatsIncluded: `• Guide + porter + tenda tim
• Konsumsi 8x selama trek
• Simaksi TN Gunung Rinjani
• Transport basecamp PP Mataram`,
whatsExcluded: `• Tiket pesawat ke Lombok
• Sleeping bag & carrier pribadi
• Asuransi perjalanan`,
destination: "Gunung Rinjani",
location: "Lombok Timur, NTB",
date: relDate(35),
endDate: relDate(37),
maxParticipants: 10,
price: 2750000,
vibe: "HARDCORE",
status: "OPEN",
images: pics(PHOTO.hiking, ["Puncak Rinjani", "Danau Segara Anak"]),
},
{
key: "open_belitung",
organizerKey: "ayu",
category: "ISLAND_HOPPING",
title: "Belitung Island Hopping 3D2N",
description: `Pulau-pulau batu granit raksasa & pantai pasir sehalus tepung.
🪨 Pulau Lengkuas (mercusuar), Pulau Batu Berlayar, Tanjung Tinggi`,
meetingPoint: "Bandara H.A.S. Hanandjoeddin, Tanjung Pandan",
whatsIncluded: `• Penginapan 2 malam
• Boat hopping + alat snorkel
• Mobil + sopir selama trip
• Makan 6x`,
whatsExcluded: `• Tiket pesawat ke Belitung
• Pengeluaran pribadi`,
destination: "Kepulauan Belitung",
location: "Belitung, Bangka Belitung",
date: relDate(38),
endDate: relDate(39),
maxParticipants: 12,
price: 1350000,
vibe: "BALANCED",
status: "OPEN",
images: pics(PHOTO.beach, ["Pulau Lengkuas", "Pantai batu granit"]),
},
{
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 JeparaKarimun
• 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: relDate(40),
endDate: relDate(42),
maxParticipants: 12,
price: 1450000,
vibe: "BALANCED",
status: "OPEN",
images: pics(PHOTO.beach, ["Pantai Karimun", "Boat hopping"]),
},
{
key: "open_ijen",
organizerKey: "rangga",
category: "HIKING",
title: "Kawah Ijen Blue Fire Tour",
description: `Fenomena api biru langka — hanya ada 2 di dunia. 🔥💙
⚠️ Trek malam, wajib masker gas (disediakan)
🌅 Lanjut sunrise di kawah belerang toska`,
meetingPoint: "Pos Paltuding, Banyuwangi, 00:30 WIB",
whatsIncluded: `• Guide lokal + masker gas
• Tiket masuk kawasan
• Sarapan + kopi`,
whatsExcluded: `• Transport ke Banyuwangi
• Sewa headlamp`,
destination: "Kawah Ijen",
location: "Banyuwangi, Jawa Timur",
date: relDate(41),
endDate: relDate(42),
maxParticipants: 12,
price: 525000,
vibe: "HARDCORE",
status: "OPEN",
images: pics(PHOTO.hiking, ["Kawah Ijen", "Sunrise di kawah"]),
},
{
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: relDate(45),
endDate: null,
maxParticipants: 6,
price: 0,
vibe: "BALANCED",
status: "OPEN",
images: pics(PHOTO.concert, ["Konser malam"]),
},
{
key: "open_komodo",
organizerKey: "ayu",
category: "ISLAND_HOPPING",
title: "Sailing Komodo 3D2N — Open Trip Phinisi",
description: `Live-on-board kapal phinisi keliling Taman Nasional Komodo. ⛵
🦎 Pulau Padar, Pink Beach, ketemu komodo, Manta Point`,
meetingPoint: "Pelabuhan Labuan Bajo, Flores, 08:00 WITA",
whatsIncluded: `• Kapal phinisi (kabin sharing) 2 malam
• Makan 8x + snack
• Alat snorkel
• Tiket masuk TN Komodo + ranger`,
whatsExcluded: `• Tiket pesawat ke Labuan Bajo
• Tipping kru kapal`,
destination: "Taman Nasional Komodo",
location: "Labuan Bajo, NTT",
date: relDate(50),
endDate: relDate(52),
maxParticipants: 16,
price: 3650000,
vibe: "BALANCED",
status: "OPEN",
images: pics(PHOTO.beach, ["Pulau Padar", "Pink Beach Komodo"]),
},
{
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: relDate(52),
endDate: relDate(54),
maxParticipants: 8,
price: 2400000,
vibe: "CHILL",
status: "OPEN",
images: pics(PHOTO.retreat, ["Yoga di sawah Ubud", "Villa retreat"]),
},
{
key: "open_yoga_retreat",
organizerKey: "fiersa",
category: "RETREAT",
title: "Weekend Yoga & Digital Detox — Selabintana",
description: `Lepas dari notifikasi 2 hari penuh di kaki Gunung Gede.
📵 Digital detox — HP dititip saat sesi
🧘 Yoga, hiking ringan, journaling`,
meetingPoint: "Resort Selabintana, Sukabumi, Sabtu 13:00 WIB",
whatsIncluded: `• Penginapan resort 1 malam
• Yoga 3 sesi + meditasi
• Hiking ringan berpemandu
• Konsumsi sehat 4x`,
whatsExcluded: `• Transport ke Sukabumi
• Treatment tambahan`,
destination: "Yoga Retreat Selabintana",
location: "Sukabumi, Jawa Barat",
date: relDate(58),
endDate: relDate(60),
maxParticipants: 10,
price: 1950000,
vibe: "CHILL",
status: "OPEN",
images: pics(PHOTO.retreat, ["Sesi yoga pagi", "Resort di kaki gunung"]),
},
];
type TripMap = Record<string, { id: string; price: number; status: string }>;
async function seedTrips(users: UserMap): Promise<TripMap> {
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
{ tripKey: "past_prau", userKey: "budi", intent: "PAID" },
{ tripKey: "past_prau", userKey: "intan", intent: "PAID" },
{ tripKey: "past_prau", userKey: "galih", intent: "PAID" },
{ tripKey: "past_nusapenida", userKey: "sari", intent: "PAID" },
{ tripKey: "past_nusapenida", userKey: "wina", intent: "PAID" },
{ tripKey: "past_nusapenida", userKey: "bayu", intent: "PAID" },
// 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 ----------
// Kota Tua
{ tripKey: "open_kotatua", userKey: "galih", intent: "PAID" },
{ tripKey: "open_kotatua", userKey: "wina", intent: "PAID" },
{ tripKey: "open_kotatua", userKey: "intan", intent: "PAID" },
{ tripKey: "open_kotatua", userKey: "raka", intent: "PENDING_REQUEST" },
// Prau
{ tripKey: "open_prau", userKey: "intan", intent: "PAID" },
{ tripKey: "open_prau", userKey: "galih", intent: "AWAITING_PAY" },
{ tripKey: "open_prau", userKey: "bayu", intent: "PENDING_REQUEST" },
// Culinary Bandung — penuh joiner
{ 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" },
// Pulau Harapan — FULL (6 peserta = maxParticipants)
{ tripKey: "open_seribu", userKey: "budi", intent: "PAID" },
{ tripKey: "open_seribu", userKey: "sari", intent: "PAID" },
{ tripKey: "open_seribu", userKey: "maya", intent: "PAID" },
{ tripKey: "open_seribu", userKey: "raka", intent: "PAID" },
{ tripKey: "open_seribu", userKey: "intan", intent: "PAID" },
{ tripKey: "open_seribu", userKey: "galih", intent: "PAID" },
// 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" },
// Bromo
{ tripKey: "open_bromo", userKey: "budi", intent: "PAID" },
{ tripKey: "open_bromo", userKey: "wina", intent: "PAID" },
{ tripKey: "open_bromo", userKey: "raka", intent: "MARKED_PAID" },
{ tripKey: "open_bromo", userKey: "intan", intent: "PENDING_REQUEST" },
// Camping Ranca Upas
{ 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" },
// Kuliner Semarang
{ tripKey: "open_semarang_kuliner", userKey: "budi", intent: "PAID" },
{ tripKey: "open_semarang_kuliner", userKey: "galih", intent: "PAID" },
{ tripKey: "open_semarang_kuliner", userKey: "bayu", intent: "MARKED_PAID" },
// Nusa Penida
{ tripKey: "open_nusapenida", userKey: "sari", intent: "PAID" },
{ tripKey: "open_nusapenida", userKey: "maya", intent: "PAID" },
{ tripKey: "open_nusapenida", userKey: "wina", intent: "MARKED_PAID" },
// City trip Jogja
{ tripKey: "open_citytrip", userKey: "budi", intent: "PAID" },
{ tripKey: "open_citytrip", userKey: "maya", intent: "PAID" },
{ tripKey: "open_citytrip", userKey: "sari", intent: "PENDING_REQUEST" },
// Ciremai
{ tripKey: "open_ciremai", userKey: "budi", intent: "PAID" },
{ tripKey: "open_ciremai", userKey: "maya", intent: "AWAITING_PAY" },
// Camping & curug Sentul
{ tripKey: "open_sentul_camp", userKey: "intan", intent: "PAID" },
{ tripKey: "open_sentul_camp", userKey: "wina", intent: "AWAITING_PAY" },
{ tripKey: "open_sentul_camp", userKey: "raka", intent: "PENDING_REQUEST" },
// Merbabu
{ tripKey: "open_merbabu", userKey: "galih", intent: "PAID" },
{ tripKey: "open_merbabu", userKey: "bayu", intent: "AWAITING_PAY" },
// Snorkeling Pahawang
{ tripKey: "open_snorkel", userKey: "sari", intent: "PAID" },
{ tripKey: "open_snorkel", userKey: "maya", intent: "MARKED_PAID" },
{ tripKey: "open_snorkel", userKey: "raka", intent: "PENDING_REQUEST" },
// Diving Lembongan
{ tripKey: "open_lembongan_dive", userKey: "doni", intent: "PAID" },
{ tripKey: "open_lembongan_dive", userKey: "bayu", intent: "AWAITING_PAY" },
// Diving Tulamben
{ tripKey: "open_diving", userKey: "doni", intent: "AWAITING_PAY" },
// Festival (FREE) — Booking langsung PAID
{ tripKey: "open_festival", userKey: "raka", intent: "FREE_CONFIRMED" },
{ tripKey: "open_festival", userKey: "bayu", intent: "FREE_CONFIRMED" },
{ tripKey: "open_festival", userKey: "galih", intent: "PENDING_REQUEST" },
// Rinjani
{ tripKey: "open_rinjani", userKey: "doni", intent: "AWAITING_PAY" },
{ tripKey: "open_rinjani", userKey: "bayu", intent: "PENDING_REQUEST" },
// Belitung
{ tripKey: "open_belitung", userKey: "wina", intent: "PAID" },
{ tripKey: "open_belitung", userKey: "bayu", intent: "PAID" },
{ tripKey: "open_belitung", userKey: "doni", intent: "PENDING_REQUEST" },
// Island hop Karimun
{ 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" },
// Kawah Ijen
{ tripKey: "open_ijen", userKey: "doni", intent: "PENDING_REQUEST" },
{ tripKey: "open_ijen", userKey: "raka", intent: "PENDING_REQUEST" },
// Concert Coldplay (FREE)
{ tripKey: "open_concert", userKey: "maya", intent: "FREE_CONFIRMED" },
{ tripKey: "open_concert", userKey: "raka", intent: "FREE_CONFIRMED" },
{ tripKey: "open_concert", userKey: "sari", intent: "PENDING_REQUEST" },
// Komodo
{ tripKey: "open_komodo", userKey: "budi", intent: "AWAITING_PAY" },
{ tripKey: "open_komodo", userKey: "intan", intent: "PENDING_REQUEST" },
// Workshop fotografi
{ tripKey: "open_workshop", userKey: "doni", intent: "PAID" },
{ tripKey: "open_workshop", userKey: "sari", intent: "AWAITING_PAY" },
// Retreat Ubud
{ tripKey: "open_retreat", userKey: "maya", intent: "PENDING_REQUEST" },
// Yoga retreat Selabintana
{ tripKey: "open_yoga_retreat", userKey: "maya", intent: "PENDING_REQUEST" },
{ tripKey: "open_yoga_retreat", userKey: "sari", 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.",
},
{
tripKey: "past_prau",
userKey: "budi",
rating: 5,
comment: "Sunrise Prau emang gak boong. Track-nya pendek, cocok bawa pemula.",
},
{
tripKey: "past_prau",
userKey: "intan",
rating: 4,
comment: "Pemandangan juara. Camp area agak rame karena weekend, tapi tim handle dengan baik.",
},
{
tripKey: "past_prau",
userKey: "galih",
rating: 5,
comment: "Bang Rangga ramah, logistik lengkap. Kopi pagi di puncak gak terlupakan.",
},
{
tripKey: "past_nusapenida",
userKey: "sari",
rating: 5,
comment: "Kelingking Beach se-keren itu aslinya. Sopir + guide-nya enak diajak ngobrol.",
},
{
tripKey: "past_nusapenida",
userKey: "wina",
rating: 4,
comment: "Spotnya bagus semua, cuma antrian foto di Kelingking lumayan panjang. Overall puas.",
},
{
tripKey: "past_nusapenida",
userKey: "bayu",
rating: 5,
comment: "Crystal Bay jernih banget buat snorkeling. Mbak Ayu organizer yang rapi.",
},
];
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 — hapus semua data 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);
const countBy = (s: SeedTrip["status"]) =>
SEED_TRIPS.filter((t) => t.status === s).length;
const organizerCount = SEED_VERIFICATIONS.filter(
(v) => v.status === "APPROVED"
).length;
console.log(`
✅ Selesai. Database fresh. Ringkasan:
${SEED_USERS.length} users (1 admin, ${organizerCount} organizer terverifikasi, sisanya peserta)
${SEED_VERIFICATIONS.length} organizer verifications (${organizerCount} APPROVED, 1 PENDING, 1 REJECTED)
${SEED_TRIPS.length} trips (${countBy("COMPLETED")} COMPLETED, ${countBy("CLOSED")} CLOSED, ${countBy("OPEN")} OPEN, ${countBy("FULL")} FULL)
${SEED_PARTICIPANTS.length} participants (mix PENDING/CONFIRMED/CANCELLED)
${SEED_REVIEWS.length} reviews
📅 Semua tanggal relatif terhadap hari seed dijalankan — trip OPEN selalu di masa depan.
🔐 Login info:
Admin : admin@setrip.id
Organizer : dede.inoen@setrip.id, panji@setrip.id, fiersa@setrip.id, rangga@setrip.id, ayu@setrip.id
Peserta : budi/sari/doni/maya/raka/intan/galih/wina/bayu @gmail.com
Password : ${SEED_PASSWORD}
`);
}
main()
.catch((e) => {
console.error("❌ Seed failed:", e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});