- Remove unused imports/variables in seed scripts and test files - Replace console.log with console.warn in seed/utility scripts - Replace `as any` with proper Prisma types (InputJsonValue, PaymentStatus, Plan, UserWhereInput) - Fix import-x/no-named-as-default-member warnings in logger, mapbox, eslint config - Prefix unused callback params with underscore in e2e tests Co-Authored-By: Paperclip <noreply@paperclip.ing>
327 lines
9.7 KiB
TypeScript
327 lines
9.7 KiB
TypeScript
import { PrismaPg } from '@prisma/adapter-pg';
|
|
import {
|
|
PrismaClient,
|
|
UserRole,
|
|
PropertyType,
|
|
TransactionType,
|
|
ListingStatus,
|
|
Direction,
|
|
} from '@prisma/client';
|
|
import pg from 'pg';
|
|
import { importMarketData } from '../scripts/import-market-data';
|
|
import { CITY_COORDINATES } from '../scripts/seed-districts';
|
|
import { seedPlans } from '../scripts/seed-plans';
|
|
|
|
const pool = new pg.Pool({ connectionString: process.env['DATABASE_URL'] });
|
|
const adapter = new PrismaPg(pool);
|
|
const prisma = new PrismaClient({ adapter });
|
|
|
|
// =============================================================================
|
|
// Sample coordinates for HCM districts
|
|
// =============================================================================
|
|
|
|
const _SAMPLE_LOCATIONS = CITY_COORDINATES['Hồ Chí Minh'];
|
|
|
|
// =============================================================================
|
|
// Seed functions
|
|
// =============================================================================
|
|
|
|
// seedPlans is imported from scripts/seed-plans.ts
|
|
|
|
async function seedUsers() {
|
|
console.log('Seeding sample users...');
|
|
|
|
const admin = await prisma.user.upsert({
|
|
where: { phone: '0900000001' },
|
|
update: {},
|
|
create: {
|
|
phone: '0900000001',
|
|
email: 'admin@goodgo.vn',
|
|
fullName: 'Admin GoodGo',
|
|
role: UserRole.ADMIN,
|
|
kycStatus: 'VERIFIED',
|
|
isActive: true,
|
|
},
|
|
});
|
|
|
|
const agent1 = await prisma.user.upsert({
|
|
where: { phone: '0900000002' },
|
|
update: {},
|
|
create: {
|
|
phone: '0900000002',
|
|
email: 'agent.nguyen@goodgo.vn',
|
|
fullName: 'Nguyễn Văn An',
|
|
role: UserRole.AGENT,
|
|
kycStatus: 'VERIFIED',
|
|
isActive: true,
|
|
},
|
|
});
|
|
|
|
const agent2 = await prisma.user.upsert({
|
|
where: { phone: '0900000003' },
|
|
update: {},
|
|
create: {
|
|
phone: '0900000003',
|
|
email: 'agent.tran@goodgo.vn',
|
|
fullName: 'Trần Thị Bình',
|
|
role: UserRole.AGENT,
|
|
kycStatus: 'VERIFIED',
|
|
isActive: true,
|
|
},
|
|
});
|
|
|
|
const buyer = await prisma.user.upsert({
|
|
where: { phone: '0900000004' },
|
|
update: {},
|
|
create: {
|
|
phone: '0900000004',
|
|
email: 'buyer.le@gmail.com',
|
|
fullName: 'Lê Minh Cường',
|
|
role: UserRole.BUYER,
|
|
kycStatus: 'NONE',
|
|
isActive: true,
|
|
},
|
|
});
|
|
|
|
const seller = await prisma.user.upsert({
|
|
where: { phone: '0900000005' },
|
|
update: {},
|
|
create: {
|
|
phone: '0900000005',
|
|
email: 'seller.pham@gmail.com',
|
|
fullName: 'Phạm Đức Dũng',
|
|
role: UserRole.SELLER,
|
|
kycStatus: 'VERIFIED',
|
|
isActive: true,
|
|
},
|
|
});
|
|
|
|
// Create agent profiles
|
|
await prisma.agent.upsert({
|
|
where: { userId: agent1.id },
|
|
update: {},
|
|
create: {
|
|
userId: agent1.id,
|
|
licenseNumber: 'BDS-2024-001',
|
|
agency: 'GoodGo Premium Realty',
|
|
qualityScore: 4.8,
|
|
totalDeals: 127,
|
|
responseTimeAvg: 15,
|
|
bio: 'Chuyên viên bất động sản cao cấp Quận 1, Quận 7 với 10 năm kinh nghiệm.',
|
|
serviceAreas: ['quan-1', 'quan-7', 'thu-duc'],
|
|
isVerified: true,
|
|
},
|
|
});
|
|
|
|
await prisma.agent.upsert({
|
|
where: { userId: agent2.id },
|
|
update: {},
|
|
create: {
|
|
userId: agent2.id,
|
|
licenseNumber: 'BDS-2024-002',
|
|
agency: 'Saigon Homes',
|
|
qualityScore: 4.5,
|
|
totalDeals: 89,
|
|
responseTimeAvg: 20,
|
|
bio: 'Chuyên gia bất động sản Bình Thạnh, Phú Nhuận. Tư vấn miễn phí.',
|
|
serviceAreas: ['binh-thanh', 'phu-nhuan', 'go-vap'],
|
|
isVerified: true,
|
|
},
|
|
});
|
|
|
|
console.log(' ✓ 5 users + 2 agent profiles seeded');
|
|
return { admin, agent1, agent2, buyer, seller };
|
|
}
|
|
|
|
async function seedProperties(users: Awaited<ReturnType<typeof seedUsers>>) {
|
|
console.log('Seeding sample properties and listings...');
|
|
|
|
const sampleProperties = [
|
|
{
|
|
propertyType: PropertyType.APARTMENT,
|
|
title: 'Căn hộ Vinhomes Central Park 3PN view sông',
|
|
description:
|
|
'Căn hộ 3 phòng ngủ tại Vinhomes Central Park, tầng cao view sông Sài Gòn. Full nội thất cao cấp, tiện ích đầy đủ.',
|
|
address: '208 Nguyễn Hữu Cảnh',
|
|
ward: 'Phường 22',
|
|
district: 'Quận Bình Thạnh',
|
|
city: 'Hồ Chí Minh',
|
|
lat: 10.7942,
|
|
lng: 106.7214,
|
|
areaM2: 108,
|
|
usableAreaM2: 95,
|
|
bedrooms: 3,
|
|
bathrooms: 2,
|
|
floor: 25,
|
|
totalFloors: 50,
|
|
direction: Direction.SOUTHEAST,
|
|
yearBuilt: 2018,
|
|
legalStatus: 'Sổ hồng',
|
|
priceVND: BigInt(8_500_000_000),
|
|
transactionType: TransactionType.SALE,
|
|
projectName: 'Vinhomes Central Park',
|
|
},
|
|
{
|
|
propertyType: PropertyType.APARTMENT,
|
|
title: 'Căn hộ The Sun Avenue 2PN cho thuê',
|
|
description: 'Cho thuê căn hộ 2PN The Sun Avenue, nội thất đầy đủ, gần Metro, view đẹp.',
|
|
address: '28 Mai Chí Thọ',
|
|
ward: 'An Phú',
|
|
district: 'Thủ Đức',
|
|
city: 'Hồ Chí Minh',
|
|
lat: 10.7696,
|
|
lng: 106.7511,
|
|
areaM2: 76,
|
|
usableAreaM2: 68,
|
|
bedrooms: 2,
|
|
bathrooms: 2,
|
|
floor: 15,
|
|
totalFloors: 28,
|
|
direction: Direction.NORTH,
|
|
yearBuilt: 2020,
|
|
legalStatus: 'Sổ hồng',
|
|
priceVND: BigInt(15_000_000),
|
|
transactionType: TransactionType.RENT,
|
|
projectName: 'The Sun Avenue',
|
|
},
|
|
{
|
|
propertyType: PropertyType.TOWNHOUSE,
|
|
title: 'Nhà phố Thảo Điền 1 trệt 3 lầu',
|
|
description:
|
|
'Nhà phố khu compound an ninh Thảo Điền, 1 trệt 3 lầu, sân vườn rộng, gara ô tô.',
|
|
address: '12 Nguyễn Văn Hưởng',
|
|
ward: 'Thảo Điền',
|
|
district: 'Thủ Đức',
|
|
city: 'Hồ Chí Minh',
|
|
lat: 10.8033,
|
|
lng: 106.7391,
|
|
areaM2: 200,
|
|
usableAreaM2: 350,
|
|
bedrooms: 4,
|
|
bathrooms: 5,
|
|
floors: 4,
|
|
direction: Direction.SOUTH,
|
|
yearBuilt: 2015,
|
|
legalStatus: 'Sổ hồng',
|
|
priceVND: BigInt(25_000_000_000),
|
|
transactionType: TransactionType.SALE,
|
|
projectName: null,
|
|
},
|
|
{
|
|
propertyType: PropertyType.LAND,
|
|
title: 'Đất nền Quận 7 gần Phú Mỹ Hưng',
|
|
description: 'Đất nền thổ cư 100%, sổ riêng, hẻm ô tô, gần trung tâm Phú Mỹ Hưng.',
|
|
address: '56 Huỳnh Tấn Phát',
|
|
ward: 'Phú Thuận',
|
|
district: 'Quận 7',
|
|
city: 'Hồ Chí Minh',
|
|
lat: 10.7312,
|
|
lng: 106.7283,
|
|
areaM2: 120,
|
|
usableAreaM2: null,
|
|
bedrooms: null,
|
|
bathrooms: null,
|
|
direction: Direction.EAST,
|
|
yearBuilt: null,
|
|
legalStatus: 'Sổ đỏ',
|
|
priceVND: BigInt(12_000_000_000),
|
|
transactionType: TransactionType.SALE,
|
|
projectName: null,
|
|
},
|
|
{
|
|
propertyType: PropertyType.OFFICE,
|
|
title: 'Văn phòng cho thuê Quận 1 - 200m²',
|
|
description:
|
|
'Văn phòng hạng B tại trung tâm Quận 1, full nội thất, hệ thống PCCC, thang máy.',
|
|
address: '123 Nguyễn Huệ',
|
|
ward: 'Bến Nghé',
|
|
district: 'Quận 1',
|
|
city: 'Hồ Chí Minh',
|
|
lat: 10.7731,
|
|
lng: 106.703,
|
|
areaM2: 200,
|
|
usableAreaM2: 180,
|
|
bedrooms: null,
|
|
bathrooms: 2,
|
|
floor: 8,
|
|
totalFloors: 15,
|
|
direction: Direction.WEST,
|
|
yearBuilt: 2010,
|
|
legalStatus: 'Sổ hồng',
|
|
priceVND: BigInt(80_000_000),
|
|
transactionType: TransactionType.RENT,
|
|
projectName: null,
|
|
},
|
|
];
|
|
|
|
const agents = await prisma.agent.findMany();
|
|
|
|
for (let i = 0; i < sampleProperties.length; i++) {
|
|
const p = sampleProperties[i]!;
|
|
const agent = agents[i % agents.length]!
|
|
|
|
const _property = await prisma.$executeRaw`
|
|
INSERT INTO "Property" (
|
|
"id", "propertyType", "title", "description", "address",
|
|
"ward", "district", "city", "location",
|
|
"areaM2", "usableAreaM2", "bedrooms", "bathrooms",
|
|
"floors", "floor", "totalFloors", "direction",
|
|
"yearBuilt", "legalStatus", "amenities", "nearbyPOIs",
|
|
"metroDistanceM", "projectName", "createdAt", "updatedAt"
|
|
) VALUES (
|
|
${`prop-${i + 1}`}, ${p.propertyType}::"PropertyType", ${p.title}, ${p.description}, ${p.address},
|
|
${p.ward}, ${p.district}, ${p.city}, ST_SetSRID(ST_MakePoint(${p.lng}, ${p.lat}), 4326),
|
|
${p.areaM2}, ${p.usableAreaM2 ?? null}, ${p.bedrooms ?? null}, ${p.bathrooms ?? null},
|
|
${p.floors ?? null}, ${p.floor ?? null}, ${p.totalFloors ?? null}, ${p.direction ?? null}::"Direction",
|
|
${p.yearBuilt ?? null}, ${p.legalStatus ?? null}, ${null}, ${null},
|
|
${null}, ${p.projectName ?? null}, NOW(), NOW()
|
|
)
|
|
ON CONFLICT ("id") DO NOTHING
|
|
`;
|
|
|
|
await prisma.listing.upsert({
|
|
where: { id: `listing-${i + 1}` },
|
|
update: {},
|
|
create: {
|
|
id: `listing-${i + 1}`,
|
|
propertyId: `prop-${i + 1}`,
|
|
agentId: agent.id,
|
|
sellerId: users.seller.id,
|
|
transactionType: p.transactionType,
|
|
status: ListingStatus.ACTIVE,
|
|
priceVND: p.priceVND,
|
|
pricePerM2: Number(p.priceVND) / p.areaM2,
|
|
publishedAt: new Date(),
|
|
},
|
|
});
|
|
}
|
|
|
|
console.log(` ✓ ${sampleProperties.length} properties + listings seeded`);
|
|
}
|
|
|
|
// seedMarketIndex is now handled by importMarketData from scripts/import-market-data.ts
|
|
|
|
// =============================================================================
|
|
// Main seed
|
|
// =============================================================================
|
|
|
|
async function main() {
|
|
console.log('🌱 Starting seed...\n');
|
|
|
|
await seedPlans();
|
|
const users = await seedUsers();
|
|
await seedProperties(users);
|
|
await importMarketData();
|
|
|
|
console.log('\n✅ Seed completed successfully!');
|
|
}
|
|
|
|
main()
|
|
.catch((e) => {
|
|
console.error('❌ Seed failed:', e);
|
|
process.exit(1);
|
|
})
|
|
.finally(async () => {
|
|
await prisma.$disconnect();
|
|
});
|