Files
setrip/prisma/seed.ts
T
2026-05-10 22:48:12 +07:00

1367 lines
46 KiB
TypeScript
Raw 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
// ============================================================================
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 — <hari>` lalu bullet `• HH:MMHH: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:0005:30 Meeting & briefing di Alun-alun Garut
• 05:3007:00 Perjalanan menuju basecamp Cisurupan
• 07:0008:00 Sarapan + repacking + pemanasan
• 08:0011:00 Trekking Camp David — Hutan Mati — Pondok Salada
• 11:0012:30 Setup tenda di Pondok Salada
• 12:3014:00 ISHOMA + games kenalan grup
• 14:0017:00 Eksplor Tegal Alun & Hutan Mati (golden hour foto)
• 17:0019:00 Masak bareng + makan malam
• 19:0021:00 Api unggun, kopi, sharing rencana summit
• 21:00 Istirahat
Hari 2 — Minggu
• 03:3004:00 Bangun + sarapan ringan + air panas
• 04:0005:30 Summit attack ke puncak Papandayan
• 05:3007:00 Sunrise + foto bareng di puncak
• 07:0009:00 Turun ke camp + sarapan utama
• 09:0011:00 Beres-beres tenda + repacking
• 11:0013:30 Turun ke basecamp Cisurupan
• 13:3014:30 Bersih-bersih + makan siang
• 14:3016:30 Perjalanan kembali ke Garut
• 16:30 Sampai Garut, bubar grup`;
const ITIN_CIREMAI = `Hari 1 — Sabtu
• 04:0004:30 Meeting & briefing di Stasiun Kuningan
• 04:3006:30 Perjalanan ke basecamp Apuy via Maja & Argapura
• 06:3007:30 Sarapan + registrasi simaksi + repacking
• 07:3010:30 Trek Pos 1 (Berod) → Pos 2 (Arban) → Pos 3 (Tegal Masawa)
• 10:3011:30 ISHOMA di Pos 4 (Tegal Jamuju)
• 11:3014:30 Lanjut Pos 5 (Sanghyang Rangkah) → Pos 6 (Goa Walet)
• 14:3016:00 Setup tenda di Pos 6
• 16:0018:00 Acara bebas + makan sore + persiapan summit
• 18:0020:00 Briefing summit + early dinner
• 20:00 Istirahat (bangun dini hari)
Hari 2 — Minggu
• 02:0002:30 Bangun + cemilan + minuman hangat
• 02:3005:00 Summit attack ke puncak Sunan Cirebon (3.078 mdpl)
• 05:0006:30 Sunrise di puncak Ciremai
• 06:3008:30 Turun ke Pos 6 + sarapan
• 08:3010:30 Beres tenda + repacking
• 10:3014:00 Turun ke basecamp Apuy (track curam — hati-hati lutut)
• 14:0015:00 Bersih-bersih + makan siang di basecamp
• 15:0017:00 Kembali ke Stasiun Kuningan
• 17:00 Bubar grup`;
const ITIN_CAMPING = `Hari 1 — Sabtu
• 13:0013:30 Meeting di Pertigaan Pasar Lembang
• 13:3015:00 Perjalanan ke Ranca Upas via Ciwidey
• 15:0016:00 Check-in + setup tenda dome (sudah disiapkan tim)
• 16:0017:30 Tour camp area + ketemu rusa-rusa
• 17:3019:00 Persiapan BBQ + nyalakan api unggun
• 19:0021:00 Makan malam BBQ
• 21:0023:00 Live music akustik + games
• 23:00 Istirahat
Hari 2 — Minggu
• 06:0007:00 Sunrise + foto di hutan pinus
• 07:0008:30 Sarapan (nasi goreng + roti bakar + kopi)
• 08:3010:00 Memberi makan rusa + sesi foto
• 10:0011:00 Beres tenda + bersih-bersih
• 11:0011:30 Pulang menuju Lembang
• 12:00 Sampai Lembang, bubar grup`;
const ITIN_PAHAWANG = `Hari 1 — Sabtu (one-day trip)
• 07:0007:30 Meeting di Dermaga Ketapang, Lampung Selatan
• 07:3008:30 Briefing safety + fitting alat snorkel
• 08:3009:30 Sailing menuju Pulau Pahawang Kecil
• 09:3011:30 Snorkeling spot Cukuh Bedil — terumbu warna-warni
• 11:3012:30 Pindah spot ke Pulau Kelagian Kecil
• 12:3014:00 Makan siang + istirahat di pasir putih
• 14:0015:30 Snorkeling Tanjung Putus + sesi foto underwater
• 15:3016:30 Sailing kembali ke dermaga
• 16:3017:00 Bersih-bersih + bubar grup`;
const ITIN_DIVING = `Hari 1 — Sabtu
• 06:3007:00 Meeting di dive shop Tulamben + welcome coffee
• 07:0008:00 Briefing dive plan + cek sertifikasi + fitting gear
• 08:0009:00 Surface interval + pengecekan tank/regulator
• 09:0010:30 Dive #1 — USS Liberty Wreck (528m, ~50 menit bottom time)
• 10:3012:00 Surface interval + brunch + log dive
• 12:0013:30 Dive #2 — Coral Garden / Drop Off (~50 menit)
• 13:3015:00 Debrief + makan siang
• 15:0017:00 Acara bebas (rest atau eksplor desa Tulamben)
• 17:0019:00 Sunset di pantai Tulamben + dinner
• 19:00 Istirahat di homestay (mandiri)
Hari 2 — Minggu
• 06:0006:30 Bangun + kopi
• 06:3007:30 Briefing dive #3 (early morning visibility tinggi)
• 07:3009:00 Dive #3 — Liberty Wreck pagi
• 09:0010:30 Surface interval + sarapan + log
• 10:3012:00 Dive #4 (opsional, fun dive shallow reef)
• 12:0013:00 Bersih gear + debrief akhir
• 13:0014:00 Makan siang penutupan
• 14:00 Bubar grup`;
const ITIN_ISLANDHOP = `Hari 1 — Jumat
• 07:0007:30 Meeting di Pelabuhan Kartini Jepara
• 07:3013:00 Penyeberangan kapal feri Jepara → Karimun Jawa
• 13:0014:00 Tiba di Pelabuhan Karimun + transfer homestay
• 14:0015:00 Check-in homestay + ISHOMA
• 15:0017:30 Sunset di Bukit Love + foto-foto
• 17:3019:00 Bersih-bersih + makan malam
• 19:0021:00 Alun-alun Karimun + jajan kuliner
• 21:00 Istirahat
Hari 2 — Sabtu
• 06:3007:30 Sarapan + briefing hopping
• 07:3009:30 Boat ke Pulau Menjangan Kecil — snorkeling spot terumbu
• 09:3011:30 Pulau Menjangan Besar — interaksi hiu (penangkaran)
• 11:3013:00 Makan siang BBQ ikan di Pulau Cemara Besar
• 13:0015:00 Pulau Cemara Kecil + foto pasir putih
• 15:0017:00 Pulau Cilik — sunset + snorkel terakhir
• 17:0018:30 Kembali ke homestay + bersih-bersih
• 18:3020:00 Makan malam seafood
• 20:00 Acara bebas
Hari 3 — Minggu
• 06:0007:00 Sunrise di Tanjung Gelam
• 07:0009:00 Sarapan + pack-up
• 09:0010:00 Belanja oleh-oleh di pelabuhan
• 10:0016:00 Penyeberangan kapal feri Karimun → Jepara
• 16:0017:00 Tiba di Pelabuhan Kartini, bubar grup`;
const ITIN_CITYTRIP = `Hari 1 — Sabtu
• 08:0008:30 Meeting di Stasiun Tugu Yogyakarta
• 08:3010:00 Sarapan Gudeg Yu Djum + briefing rute
• 10:0012:00 Kotagede — kerajinan perak + Masjid Mataram
• 12:0013:30 Makan siang Sate Klathak Pak Pong
• 13:3016:00 Tamansari — pemandian Sultan + sumur Gumuling
• 16:0017:30 Coffee break di kedai lokal Prawirotaman
• 17:3019:30 Sunset di Bukit Bintang (Jl. Imogiri)
• 19:3021:30 Angkringan Lik Man — kopi joss + nasi kucing
• 21:30 Drop ke penginapan masing-masing
Hari 2 — Minggu
• 06:0007:00 Pickup dari penginapan
• 07:0009:30 Perjalanan ke Kalibiru, Kulon Progo
• 09:3011:30 Kalibiru — spot foto rumah pohon di tebing
• 11:3013:00 Makan siang pecel di warung lokal
• 13:0015:00 Pinus Pengger — instalasi seni alam
• 15:0016:30 Heha Sky View (opsional, cek cuaca)
• 16:3018:00 Kembali ke kota — drop di Stasiun Tugu / Bandara
• 18:00 Bubar grup`;
const ITIN_CULINARY = `Hari 1 — Sabtu (one-day food tour)
• 09:0009:30 Meeting di Stasiun Bandung pintu utara + briefing rute
• 09:3010:15 Stop 1: Surabi Enhaii (sarapan tradisional)
• 10:1511:00 Stop 2: Lotek Kalipah Apo
• 11:0011:45 Stop 3: Mie Kocok Mang Dadeng (Kebon Jukut)
• 11:4512:30 Stop 4: Bakso Akung (cabang Burangrang)
• 12:3013:30 Istirahat + jalan santai di Cihampelas
• 13:3014:15 Stop 5: Batagor Kingsley
• 14:1515:00 Stop 6: Cuanki Serayu
• 15:0015:45 Stop 7: Es Cendol Elizabeth
• 15:4516:30 Stop 8: Roti Gempol & Kopi Anjis (penutup)
• 16:3017:00 Closing + foto bareng di Braga`;
const ITIN_CONCERT = `Hari 1 — Sabtu (showtime)
• 17:0017:30 Meeting di Plaza GBK, depan loket Cat 1
• 17:3018:30 Foto bareng pre-show + obrolan singkat
• 18:3019:00 Masuk venue bareng (kategori tetap masing-masing)
• 19:0022:30 Konser Coldplay — Music of the Spheres
• 22:3023:00 Berkumpul lagi di luar gerbang utama GBK
• 23:0000:30 After-party dinner di Senayan (resto TBA via grup WA)
• 00:30 Bubar`;
const ITIN_WORKSHOP = `Hari 1 — Sabtu
• 04:0004:30 Meeting di Alun-alun Pangalengan
• 04:3005:30 Briefing teknis + setup peralatan
• 05:3007:30 Sunrise shoot di Perkebunan Teh Malabar
• 07:3009:00 Sarapan + sesi review foto bareng mentor
• 09:0011:00 Materi indoor: long exposure & filter ND
• 11:0013:00 ISHOMA + transfer ke Situ Cileunca
• 13:0016:00 Workshop on-field di Situ Cileunca (panorama, refleksi)
• 16:0018:00 Golden hour shoot di Bukit Nini
• 18:0019:30 Makan malam di villa
• 19:3022:00 Sesi malam — milky way / star trail (cuaca permitting)
• 22:00 Istirahat di villa
Hari 2 — Minggu
• 05:0007:00 Sunrise shoot di Pangalengan + foto siluet
• 07:0008:30 Sarapan + diskusi hasil
• 08:3011:00 Sesi editing Lightroom (laptop pribadi)
• 11:0012:00 Review akhir + sertifikat
• 12:0013:00 Makan siang penutupan
• 13:0015:00 Kembali ke Alun-alun Pangalengan
• 15:00 Bubar grup`;
const ITIN_RETREAT = `Hari 1 — Jumat
• 14:0015:00 Check-in Villa Sawah Ubud + welcome drink (jamu)
• 15:0016:00 Tour fasilitas + pembagian welcome kit
• 16:0017:30 Yin Yoga pembuka — release perjalanan
• 17:3018:30 Journaling: niat & ekspektasi retreat
• 18:3020:00 Dinner vegan (set menu)
• 20:0021:00 Circle pengenalan + meditasi singkat
• 21:00 Lights-out
Hari 2 — Sabtu
• 06:0007:30 Hatha Yoga matahari terbit
• 07:3009:00 Sarapan vegan + tea ceremony
• 09:0010:30 Meditasi guided: body scan
• 10:3012:00 Pranayama (latihan napas)
• 12:0013:30 Lunch + acara bebas (sawah walk)
• 13:3015:00 Sound healing dengan singing bowl
• 15:0016:30 Workshop: mindful eating + jamu making
• 16:3018:00 Yin Yoga sore + savasana panjang
• 18:0019:30 Dinner vegan
• 19:3021:00 Sharing circle + meditasi malam
• 21:00 Lights-out
Hari 3 — Minggu
• 06:0007:30 Vinyasa Flow penutupan
• 07:3009:00 Sarapan + closing journaling
• 09:0010:30 Closing circle + tukar pesan
• 10:3011:30 Check-out + pelukan perpisahan
• 11:3014: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.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();
}
// ============================================================================
// 2. Users + Profile
// ============================================================================
interface SeedUser {
key: string;
name: string;
email: string;
isAdmin?: boolean;
profile?: {
bio?: string;
city?: string;
interests?: string[];
instagram?: string;
vibe?: Vibe;
};
}
const SEED_USERS: SeedUser[] = [
// Admin
{
key: "admin",
name: "Admin Setrip",
email: "admin@setrip.id",
isAdmin: true,
},
// Organizers (akan di-link ke OrganizerVerification)
{
key: "dede",
name: "Dede Inoen",
email: "dede.inoen@setrip.id",
profile: {
bio: "Pendaki sejak 2015. Suka trip santai, bawa peserta pulang dengan selamat.",
city: "Garut",
interests: ["hiking", "camping", "fotografi"],
instagram: "dede.inoen",
vibe: "BALANCED",
},
},
{
key: "panji",
name: "Panji Petualang",
email: "panji@setrip.id",
profile: {
bio: "Trip leader 10+ tahun. Spesialis gunung & laut.",
city: "Kuningan",
interests: ["hiking", "diving", "snorkeling"],
instagram: "panji.adv",
vibe: "HARDCORE",
},
},
{
key: "fiersa",
name: "Fiersa Besari",
email: "fiersa@setrip.id",
profile: {
bio: "Buku, kopi, gunung. Kadang ngajak nonton konser bareng.",
city: "Bandung",
interests: ["hiking", "kuliner", "musik"],
instagram: "fiersabesari",
vibe: "CHILL",
},
},
// Peserta
{
key: "budi",
name: "Budi Santoso",
email: "budi@gmail.com",
profile: {
bio: "Office worker yang lagi cari teman buat jalan akhir pekan.",
city: "Jakarta",
interests: ["hiking", "kuliner"],
vibe: "BALANCED",
},
},
{
key: "sari",
name: "Sari Dewi",
email: "sari@gmail.com",
profile: {
bio: "Solo traveler. Suka pantai, ikan, dan tidur siang di hammock.",
city: "Surabaya",
interests: ["snorkeling", "yoga", "fotografi"],
instagram: "saridew_id",
vibe: "CHILL",
},
},
{
key: "doni",
name: "Doni Prasetyo",
email: "doni@gmail.com",
profile: {
bio: "Diver. Cari teman buddy fun-dive sebulan sekali.",
city: "Denpasar",
interests: ["diving", "freediving"],
vibe: "HARDCORE",
},
},
{
key: "maya",
name: "Maya Putri",
email: "maya@gmail.com",
profile: {
bio: "Yoga teacher. Lagi belajar lebih hadir di setiap perjalanan.",
city: "Yogyakarta",
interests: ["yoga", "retreat", "kuliner"],
instagram: "maya.putri",
vibe: "CHILL",
},
},
{
key: "raka",
name: "Raka Aditya",
email: "raka@gmail.com",
profile: {
bio: "Anak baru di dunia trip. Energy tinggi, niat banyak.",
city: "Bandung",
interests: ["hiking", "konser"],
vibe: "HARDCORE",
},
},
];
type UserMap = Record<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",
},
// 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" },
],
},
{
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: [],
},
// ============ 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: 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: utc(2026, 5, 13, 22, 0),
endDate: utc(2026, 5, 14, 18, 0),
maxParticipants: 10,
price: 250000,
vibe: "BALANCED",
status: "OPEN",
images: [
{ url: img("1554629947-334ff61d85dc"), caption: "Kawah Papandayan" },
{ url: img("1464822759023-fed622ff2c3b"), caption: "Track menuju puncak" },
],
},
{
key: "open_ciremai",
organizerKey: "panji",
category: "HIKING",
title: "Pendakian Ciremai via Apuy",
description: `Trip ke puncak tertinggi Jawa Barat! 🏔️
📍 Meeting Point: Stasiun Kuningan, 04:00 WIB
⚠️ Level: Menengah — perlu stamina baik`,
meetingPoint: "Stasiun Kuningan, Sabtu 04:00 WIB",
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",
itinerary: ITIN_CAMPING,
whatsIncluded: `• Tenda + sleeping bag + matras
• Logistik camp
• Makan malam BBQ + sarapan
• Tiket masuk lokasi`,
whatsExcluded: `• Transport pribadi
• Snack tambahan`,
destination: "Ranca Upas",
location: "Bandung Selatan, Jawa Barat",
date: utc(2026, 5, 16, 6, 0),
endDate: utc(2026, 5, 17, 12, 0),
maxParticipants: 12,
price: 220000,
vibe: "CHILL",
status: "OPEN",
images: [
{ url: img("1504280390367-361c6d9f38f4"), caption: "Tenda di hutan pinus" },
{ url: img("1517824806704-9040b037703b"), caption: "Sunset di camp" },
],
},
{
key: "open_snorkel",
organizerKey: "panji",
category: "SNORKELING",
title: "Snorkeling Pulau Pahawang",
description: `Air kristal-jernih, ikan warna-warni, dan pasir putih lembut.
🤿 Pemula friendly — guide profesional
📷 Underwater photo session included`,
meetingPoint: "Dermaga Ketapang, Lampung Selatan, 07:00 WIB",
itinerary: ITIN_PAHAWANG,
whatsIncluded: `• Boat PP
• Alat snorkel (masker, fin, life vest)
• Guide & pemandu underwater
• Konsumsi 2x`,
whatsExcluded: `• Transport ke Lampung
• Penginapan Sabtu malam`,
destination: "Pulau Pahawang Kecil",
location: "Lampung Selatan, Lampung",
date: utc(2026, 5, 30, 0, 0),
endDate: null,
maxParticipants: 10,
price: 380000,
vibe: "CHILL",
status: "OPEN",
images: [
{ url: img("1583212292454-1fe6229603b7"), caption: "Snorkeling di Pahawang" },
{ url: img("1559825481-12a05cc00344"), caption: "Pasir putih Pahawang Kecil" },
],
},
{
key: "open_diving",
organizerKey: "fiersa",
category: "DIVING",
title: "Fun Dive Tulamben — USS Liberty Wreck",
description: `Dive trip ke USS Liberty Wreck.
⚠️ Sertifikasi minimal: Open Water (PADI/SSI)`,
meetingPoint: "Dive shop Tulamben, 06:30 WITA",
itinerary: ITIN_DIVING,
whatsIncluded: `• 2x dive guided
• Full gear rental
• Tank & weight
• Konsumsi siang`,
whatsExcluded: `• Transport ke Bali
• Penginapan
• Sertifikasi (cek validitas)`,
destination: "USS Liberty Wreck",
location: "Tulamben, Karangasem, Bali",
date: utc(2026, 6, 4, 0, 0),
endDate: utc(2026, 6, 5, 12, 0),
maxParticipants: 6,
price: 1850000,
vibe: "HARDCORE",
status: "OPEN",
images: [
{ url: img("1544551763-46a013bb70d5"), caption: "Wreck dive Tulamben" },
{ url: img("1566024287286-457247b70310"), caption: "Reef Tulamben" },
],
},
{
key: "open_islandhop",
organizerKey: "panji",
category: "ISLAND_HOPPING",
title: "Karimun Jawa Island Hopping 3D2N",
description: `Hopping 5 pulau favorit di Karimun Jawa.
🏝️ Cocok untuk solo traveler & couple`,
meetingPoint: "Pelabuhan Kartini Jepara, Jumat 07:00 WIB",
itinerary: 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: 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",
itinerary: ITIN_CITYTRIP,
whatsIncluded: `• Transport mobil grup 2 hari
• Tour leader lokal
• Makan 3x (kuliner lokal)
• Tiket masuk semua spot`,
whatsExcluded: `• Transport ke Jogja
• Penginapan (rekomendasi disediakan)`,
destination: "Yogyakarta",
location: "Yogyakarta",
date: utc(2026, 5, 22, 0, 0),
endDate: utc(2026, 5, 23, 20, 0),
maxParticipants: 8,
price: 650000,
vibe: "BALANCED",
status: "OPEN",
images: [
{ url: img("1596402184320-417e7178b2cd"), caption: "Tamansari" },
{ url: img("1583309217394-d191d747bc66"), caption: "Sudut Jogja" },
],
},
{
key: "open_culinary",
organizerKey: "dede",
category: "CULINARY",
title: "Kulineran Street Food Bandung",
description: `Hopping 8 spot kuliner legend Bandung dalam satu hari.
🍜 Cocok buat foodie & first-timer`,
meetingPoint: "Stasiun Bandung pintu utara, 09:00 WIB",
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,
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",
itinerary: ITIN_CONCERT,
whatsIncluded: `• Koordinasi grup
• Foto bareng pre-show
• After-party dinner di Senayan`,
whatsExcluded: `• Tiket konser (bawa sendiri!)
• Transport ke GBK`,
destination: "Coldplay — Music of the Spheres",
location: "Stadion Utama GBK, Jakarta",
date: utc(2026, 6, 20, 10, 0),
endDate: null,
maxParticipants: 6,
price: 0, // Free trip — booking langsung PAID saat di-confirm
vibe: "BALANCED",
status: "OPEN",
images: [
{ url: img("1470229722913-7c0e2dbbafd3"), caption: "Konser malam" },
],
},
{
key: "open_workshop",
organizerKey: "panji",
category: "WORKSHOP",
title: "Workshop Fotografi Lanskap — Pangalengan",
description: `Belajar fotografi lanskap langsung di lapangan.
📷 Bawa kamera DSLR/mirrorless + tripod
👨‍🏫 Mentor: fotografer pro (10+ tahun pengalaman)`,
meetingPoint: "Alun-alun Pangalengan, Sabtu 04:00 WIB",
itinerary: ITIN_WORKSHOP,
whatsIncluded: `• Materi workshop (briefing + on-field)
• Tour leader & mentor
• Penginapan villa 1 malam
• Konsumsi 3x`,
whatsExcluded: `• Kamera & tripod (bawa sendiri)
• Transport ke Pangalengan`,
destination: "Fotografi Lanskap",
location: "Pangalengan, Bandung Selatan",
date: utc(2026, 6, 6, 0, 0),
endDate: utc(2026, 6, 7, 18, 0),
maxParticipants: 6,
price: 850000,
vibe: "BALANCED",
status: "OPEN",
images: [
{ url: img("1452587925148-ce544e77e70d"), caption: "Sunrise kebun teh" },
{ url: img("1444080748397-f442aa95c3e5"), caption: "Workshop on-field" },
],
},
{
key: "open_retreat",
organizerKey: "fiersa",
category: "RETREAT",
title: "Mindfulness Retreat 3D — Ubud",
description: `Retreat 3 hari di tengah sawah Ubud.
🧘 Untuk yang lagi burnout & butuh reset
👥 Grup kecil (max 8) — pengalaman akrab`,
meetingPoint: "Villa Sawah Ubud (alamat dikirim H-3 via WA)",
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
• Treatment spa opsional`,
destination: "Mindfulness Retreat Ubud",
location: "Ubud, Gianyar, Bali",
date: utc(2026, 6, 26, 0, 0),
endDate: utc(2026, 6, 28, 14, 0),
maxParticipants: 8,
price: 2400000,
vibe: "CHILL",
status: "OPEN",
images: [
{ url: img("1545389336-cf090694435e"), caption: "Yoga di sawah Ubud" },
{ url: img("1518609878373-06d740f60d8b"), caption: "Villa retreat" },
],
},
];
type TripMap = Record<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,
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()
.catch((e) => {
console.error("❌ Seed failed:", e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});