lomavuokraus/prisma/seed.js
2025-11-24 20:22:52 +02:00

553 lines
20 KiB
JavaScript

/* eslint-disable no-console */
const path = require('path');
const fs = require('fs');
require('dotenv').config({ path: path.join(__dirname, '..', '.env') });
if (fs.existsSync(path.join(__dirname, '..', 'creds', '.env'))) {
require('dotenv').config({ path: path.join(__dirname, '..', 'creds', '.env') });
}
const bcrypt = require('bcryptjs');
const { PrismaClient, Role, UserStatus, ListingStatus } = require('@prisma/client');
const { PrismaPg } = require('@prisma/adapter-pg');
const { Pool } = require('pg');
if (!process.env.DATABASE_URL) {
console.error('DATABASE_URL is not set; cannot seed.');
process.exit(1);
}
const prisma = new PrismaClient({
adapter: new PrismaPg(new Pool({ connectionString: process.env.DATABASE_URL })),
});
const SAMPLE_SLUG = 'saimaa-lakeside-cabin';
const DEFAULT_LOCALE = 'en';
const SAMPLE_EMAIL = 'host@lomavuokraus.fi';
async function main() {
const adminEmail = process.env.ADMIN_EMAIL;
const adminPassword = process.env.ADMIN_INITIAL_PASSWORD;
if (!adminEmail || !adminPassword) {
console.warn('ADMIN_EMAIL or ADMIN_INITIAL_PASSWORD missing; admin user will not be seeded.');
}
let adminUser = null;
if (adminEmail && adminPassword) {
const adminHash = await bcrypt.hash(adminPassword, 12);
adminUser = await prisma.user.upsert({
where: { email: adminEmail },
update: {
passwordHash: adminHash,
role: Role.ADMIN,
status: UserStatus.ACTIVE,
emailVerifiedAt: new Date(),
approvedAt: new Date(),
},
create: {
email: adminEmail,
passwordHash: adminHash,
role: Role.ADMIN,
status: UserStatus.ACTIVE,
emailVerifiedAt: new Date(),
approvedAt: new Date(),
},
});
}
const sampleHostHash = await bcrypt.hash('changeme-sample', 12);
const owner = await prisma.user.upsert({
where: { email: SAMPLE_EMAIL },
update: {
name: 'Sample Host',
phone: '+358401234567',
role: 'USER',
passwordHash: sampleHostHash,
status: UserStatus.ACTIVE,
emailVerifiedAt: new Date(),
approvedAt: new Date(),
},
create: {
email: SAMPLE_EMAIL,
name: 'Sample Host',
phone: '+358401234567',
role: 'USER',
passwordHash: sampleHostHash,
status: UserStatus.ACTIVE,
emailVerifiedAt: new Date(),
approvedAt: new Date(),
},
});
const listings = [
{
slug: SAMPLE_SLUG,
city: 'Punkaharju',
region: 'South Karelia',
country: 'Finland',
streetAddress: 'Saimaan rantatie 12',
addressNote: 'Lakeside trail, 5 min from main road',
latitude: 61.756,
longitude: 29.328,
maxGuests: 6,
bedrooms: 3,
beds: 4,
bathrooms: 1,
hasSauna: true,
hasFireplace: true,
hasWifi: true,
hasAirConditioning: false,
petsAllowed: false,
byTheLake: true,
evCharging: 'FREE',
priceHintPerNightCents: 14500,
cover: {
url: 'https://images.unsplash.com/photo-1505691938895-1758d7feb511?auto=format&fit=crop&w=1600&q=80',
altText: 'Lakeside cabin with sauna',
},
images: [
{ url: 'https://images.unsplash.com/photo-1505693416388-ac5ce068fe85?auto=format&fit=crop&w=1600&q=80', altText: 'Wood-fired sauna by the lake' },
{ url: 'https://images.unsplash.com/photo-1470246973918-29a93221c455?auto=format&fit=crop&w=1600&q=80', altText: 'Living area with fireplace' },
],
titleEn: 'Saimaa lakeside cabin with sauna',
teaserEn: 'Sauna, lake view, private dock, and cozy fireplace.',
descEn:
'Classic timber cabin right on Lake Saimaa. Wood-fired sauna, private dock, and a short forest walk to the nearest village. Perfect for slow weekends and midsummer gatherings.',
titleFi: 'Saimaan rantamökki saunalla',
teaserFi: 'Puusauna, järvinäköala, oma laituri ja takka.',
descFi:
'Perinteinen hirsimökki Saimaan rannalla. Puusauna, oma laituri ja lyhyt metsäreitti kylään. Sopii täydellisesti viikonloppuihin ja juhannukseen.',
},
{
slug: 'helsinki-design-loft',
city: 'Helsinki',
region: 'Uusimaa',
country: 'Finland',
streetAddress: 'Katajanokanranta 4',
addressNote: 'Buzz 12B, elevator to 5th floor',
latitude: 60.1675,
longitude: 24.9529,
maxGuests: 4,
bedrooms: 1,
beds: 2,
bathrooms: 1,
hasSauna: false,
hasFireplace: false,
hasWifi: true,
hasAirConditioning: true,
petsAllowed: false,
byTheLake: false,
evCharging: 'PAID',
priceHintPerNightCents: 16500,
cover: {
url: 'https://images.unsplash.com/photo-1505693415763-3bd1620f58c3?auto=format&fit=crop&w=1600&q=80',
altText: 'Modern loft living room',
},
images: [
{ url: 'https://images.unsplash.com/photo-1505691938895-1758d7feb511?auto=format&fit=crop&w=1600&q=80', altText: 'Balcony view' },
{ url: 'https://images.unsplash.com/photo-1505691938895-1758d7feb511?auto=format&fit=crop&w=1600&q=80', altText: 'Cozy bedroom' },
],
titleEn: 'Helsinki design loft with AC',
teaserEn: 'Top-floor loft, AC, fast Wi-Fi, tram at the door.',
descEn: 'Bright design loft near the harbor. Air conditioning, fiber Wi-Fi, and views over Katajanokka. Perfect city break base.',
titleFi: 'Helsingin design-lofti ilmastoinnilla',
teaserFi: 'Ylimmän kerroksen loft, ilmastointi ja nopea netti.',
descFi: 'Valoisa loft Katajanokalla. Ilmastointi, kuitunetti ja näkymä merelle. Täydellinen kaupunkiloma.',
},
{
slug: 'turku-riverside-apartment',
city: 'Turku',
region: 'Varsinais-Suomi',
country: 'Finland',
streetAddress: 'Läntinen Rantakatu 10',
addressNote: 'Self check-in lockbox',
latitude: 60.4518,
longitude: 22.2666,
maxGuests: 3,
bedrooms: 1,
beds: 2,
bathrooms: 1,
hasSauna: false,
hasFireplace: false,
hasWifi: true,
hasAirConditioning: false,
petsAllowed: true,
byTheLake: false,
evCharging: 'NONE',
priceHintPerNightCents: 11000,
cover: {
url: 'https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?auto=format&fit=crop&w=1600&q=80',
altText: 'Apartment living room',
},
images: [
{ url: 'https://images.unsplash.com/photo-1505691938895-1758d7feb511?auto=format&fit=crop&w=1600&q=80', altText: 'Kitchen area' },
],
titleEn: 'Riverside apartment in Turku',
teaserEn: 'By the Aura river, pet-friendly, cozy base.',
descEn: 'Compact one-bedroom next to the Aura river. Cafés outside, pet-friendly, fiber internet for workations.',
titleFi: 'Aurajoen varrella, lemmikkiystävällinen',
teaserFi: 'Aurajoen kupeessa, lemmikit sallittu.',
descFi: 'Kompakti yksiö Aurajoen varrella. Kahvilat vieressä, lemmikit sallittu, kuitunetti etätöihin.',
},
{
slug: 'rovaniemi-aurora-cabin',
city: 'Rovaniemi',
region: 'Lapland',
country: 'Finland',
streetAddress: 'Ounasjoenkuja 8',
addressNote: 'Snow tires required in winter',
latitude: 66.5039,
longitude: 25.7294,
maxGuests: 5,
bedrooms: 2,
beds: 4,
bathrooms: 1,
hasSauna: true,
hasFireplace: true,
hasWifi: true,
hasAirConditioning: false,
petsAllowed: false,
byTheLake: true,
evCharging: 'FREE',
priceHintPerNightCents: 18900,
cover: {
url: 'https://images.unsplash.com/photo-1505693416388-ac5ce068fe85?auto=format&fit=crop&w=1600&q=80',
altText: 'Aurora cabin by the river',
},
images: [
{ url: 'https://images.unsplash.com/photo-1470246973918-29a93221c455?auto=format&fit=crop&w=1600&q=80', altText: 'Fireplace lounge' },
],
titleEn: 'Aurora riverside cabin',
teaserEn: 'Sauna, fireplace, river views, EV charging.',
descEn: 'Timber cabin on the Ounasjoki riverside. Wood sauna, fireplace, glass lounge for auroras, free EV charging.',
titleFi: 'Revontulikämppä joen rannalla',
teaserFi: 'Sauna, takka, jokinäkymä ja ilmainen lataus.',
descFi: 'Hirsimökki Ounasjoen rannalla. Puusauna, takka ja lasikuisti revontulien katseluun, ilmainen sähköauton lataus.',
},
{
slug: 'tampere-sauna-studio',
city: 'Tampere',
region: 'Pirkanmaa',
country: 'Finland',
streetAddress: 'Hämeenkatu 25',
addressNote: 'Key pickup from lobby',
latitude: 61.4981,
longitude: 23.7608,
maxGuests: 2,
bedrooms: 0,
beds: 1,
bathrooms: 1,
hasSauna: true,
hasFireplace: false,
hasWifi: true,
hasAirConditioning: true,
petsAllowed: false,
byTheLake: false,
evCharging: 'NONE',
priceHintPerNightCents: 9500,
cover: {
url: 'https://images.unsplash.com/photo-1505691938895-1758d7feb511?auto=format&fit=crop&w=1600&q=80',
altText: 'Studio interior',
},
images: [],
titleEn: 'Tampere studio with private sauna',
teaserEn: 'City center, private sauna, AC and fiber.',
descEn: 'Compact studio on Hämeenkatu with private electric sauna, air conditioning, and fiber internet. Steps from tram.',
titleFi: 'Tampereen keskustastudio saunalla',
teaserFi: 'Yksityinen sauna, ilmastointi, kuitu.',
descFi: 'Kompakti studio Hämeenkadulla. Oma sähkösauna, ilmastointi ja kuitunetti. Ratikka vieressä.',
},
{
slug: 'vaasa-seaside-villa',
city: 'Vaasa',
region: 'Ostrobothnia',
country: 'Finland',
streetAddress: 'Rantakatu 3',
addressNote: 'Parking for 3 cars',
latitude: 63.096,
longitude: 21.6158,
maxGuests: 8,
bedrooms: 4,
beds: 6,
bathrooms: 2,
hasSauna: true,
hasFireplace: true,
hasWifi: true,
hasAirConditioning: false,
petsAllowed: true,
byTheLake: true,
evCharging: 'PAID',
priceHintPerNightCents: 24500,
cover: {
url: 'https://images.unsplash.com/photo-1505691938895-1758d7feb511?auto=format&fit=crop&w=1600&q=80',
altText: 'Seaside villa deck',
},
images: [],
titleEn: 'Seaside villa in Vaasa',
teaserEn: 'Deck, sauna, pet-friendly, paid EV charging.',
descEn: 'Spacious villa on the coast with large deck, wood sauna, fireplace lounge. Pets welcome; paid EV charger on site.',
titleFi: 'Rantahuvila Vaasassa',
teaserFi: 'Terassi, sauna, lemmikit ok, maksullinen lataus.',
descFi: 'Tilava huvila meren rannalla, iso terassi, puusauna ja takkahuone. Lemmikit sallittu; maksullinen latauspiste.',
},
{
slug: 'kuopio-lakeside-apartment',
city: 'Kuopio',
region: 'Northern Savonia',
country: 'Finland',
streetAddress: 'Satamakatu 7',
addressNote: 'Underground parking',
latitude: 62.8924,
longitude: 27.6783,
maxGuests: 4,
bedrooms: 2,
beds: 3,
bathrooms: 1,
hasSauna: true,
hasFireplace: false,
hasWifi: true,
hasAirConditioning: false,
petsAllowed: false,
byTheLake: true,
evCharging: 'FREE',
priceHintPerNightCents: 12900,
cover: {
url: 'https://images.unsplash.com/photo-1470246973918-29a93221c455?auto=format&fit=crop&w=1600&q=80',
altText: 'Lake view balcony',
},
images: [],
titleEn: 'Kuopio lakeside apartment with sauna',
teaserEn: 'Balcony to Kallavesi, sauna, free EV charging.',
descEn: 'Two-bedroom apartment overlooking Lake Kallavesi. Glass balcony, electric sauna, underground parking with free EV charging.',
titleFi: 'Kuopion järvinäkymä ja sauna',
teaserFi: 'Parveke Kallavedelle, sauna, ilmainen lataus.',
descFi: 'Kaksio Kallaveden rannalla. Lasitettu parveke, sähkösauna, hallipaikka ja ilmainen sähköauton lataus.',
},
{
slug: 'porvoo-river-loft',
city: 'Porvoo',
region: 'Uusimaa',
country: 'Finland',
streetAddress: 'Mannerheiminkatu 12',
addressNote: 'Historic building, stairs only',
latitude: 60.3943,
longitude: 25.6659,
maxGuests: 2,
bedrooms: 1,
beds: 1,
bathrooms: 1,
hasSauna: false,
hasFireplace: true,
hasWifi: true,
hasAirConditioning: false,
petsAllowed: false,
byTheLake: false,
evCharging: 'NONE',
priceHintPerNightCents: 9900,
cover: {
url: 'https://images.unsplash.com/photo-1505691938895-1758d7feb511?auto=format&fit=crop&w=1600&q=80',
altText: 'Loft interior',
},
images: [],
titleEn: 'Porvoo old town river loft',
teaserEn: 'Historic charm, fireplace, steps from river.',
descEn: 'Cozy loft in Porvoo old town. Brick walls, fireplace, and views toward the riverside warehouses.',
titleFi: 'Porvoon jokilofti',
teaserFi: 'Takka ja vanhan kaupungin tunnelma.',
descFi: 'Kotoisa loft Porvoon vanhassa kaupungissa. Tiiliseinät, takka ja näkymä jokirantaan.',
},
{
slug: 'oulu-tech-apartment',
city: 'Oulu',
region: 'Northern Ostrobothnia',
country: 'Finland',
streetAddress: 'Technopolis 2',
addressNote: 'Smart lock entry',
latitude: 65.0121,
longitude: 25.4651,
maxGuests: 2,
bedrooms: 1,
beds: 1,
bathrooms: 1,
hasSauna: false,
hasFireplace: false,
hasWifi: true,
hasAirConditioning: true,
petsAllowed: false,
byTheLake: false,
evCharging: 'FREE',
priceHintPerNightCents: 10500,
cover: {
url: 'https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?auto=format&fit=crop&w=1600&q=80',
altText: 'Modern apartment',
},
images: [],
titleEn: 'Smart apartment in Oulu',
teaserEn: 'AC, smart lock, free EV charging in garage.',
descEn: 'Modern one-bedroom near Technopolis. Air conditioning, smart lock, desk for work, garage with free EV chargers.',
titleFi: 'Moderni Oulun yksiö',
teaserFi: 'Ilmastointi, älylukko, ilmainen lataus.',
descFi: 'Moderni yksiö Technopoliksen lähellä. Ilmastointi, älylukko, työpiste ja ilmaiset latauspaikat hallissa.',
},
{
slug: 'mariehamn-harbor-flat',
city: 'Mariehamn',
region: 'Åland',
country: 'Finland',
streetAddress: 'Hamngatan 5',
addressNote: 'Ferry terminal 10 min walk',
latitude: 60.0973,
longitude: 19.9348,
maxGuests: 3,
bedrooms: 1,
beds: 2,
bathrooms: 1,
hasSauna: false,
hasFireplace: false,
hasWifi: true,
hasAirConditioning: false,
petsAllowed: false,
byTheLake: true,
evCharging: 'PAID',
priceHintPerNightCents: 11500,
cover: {
url: 'https://images.unsplash.com/photo-1470246973918-29a93221c455?auto=format&fit=crop&w=1600&q=80',
altText: 'Harbor view',
},
images: [],
titleEn: 'Harbor flat in Mariehamn',
teaserEn: 'Walk to ferries, harbor views, paid EV nearby.',
descEn: 'Bright flat near the harbor. Walk to ferries and restaurants, harbor-facing balcony, paid EV charging at the public lot.',
titleFi: 'Satamahuoneisto Maarianhaminassa',
teaserFi: 'Satamanäkymä, kävely lautoille.',
descFi: 'Valoisa huoneisto sataman tuntumassa. Parveke satamaan, ravintolat lähellä, maksullinen lataus viereisellä parkkipaikalla.',
},
];
for (const item of listings) {
const existing = await prisma.listingTranslation.findFirst({ where: { slug: item.slug }, select: { listingId: true } });
if (!existing) {
const created = await prisma.listing.create({
data: {
ownerId: owner.id,
status: ListingStatus.PUBLISHED,
approvedAt: new Date(),
approvedById: adminUser ? adminUser.id : owner.id,
country: item.country,
region: item.region,
city: item.city,
streetAddress: item.streetAddress,
addressNote: item.addressNote,
latitude: item.latitude,
longitude: item.longitude,
maxGuests: item.maxGuests,
bedrooms: item.bedrooms,
beds: item.beds,
bathrooms: item.bathrooms,
hasSauna: item.hasSauna,
hasFireplace: item.hasFireplace,
hasWifi: item.hasWifi,
hasAirConditioning: item.hasAirConditioning,
petsAllowed: item.petsAllowed,
byTheLake: item.byTheLake,
evCharging: item.evCharging,
priceHintPerNightCents: item.priceHintPerNightCents,
contactName: 'Sample Host',
contactEmail: SAMPLE_EMAIL,
contactPhone: owner.phone,
published: true,
translations: {
createMany: {
data: [
{ locale: 'en', slug: item.slug, title: item.titleEn, teaser: item.teaserEn, description: item.descEn },
{ locale: 'fi', slug: item.slug, title: item.titleFi, teaser: item.teaserFi, description: item.descFi },
],
},
},
images: {
create: [
{ url: item.cover.url, altText: item.cover.altText, order: 1, isCover: true },
...item.images.map((img, idx) => ({
url: img.url,
altText: img.altText ?? null,
order: idx + 2,
isCover: false,
})),
],
},
},
});
console.log('Seeded listing:', created.id, item.slug);
continue;
}
const listingId = existing.listingId;
await prisma.listing.update({
where: { id: listingId },
data: {
country: item.country,
region: item.region,
city: item.city,
streetAddress: item.streetAddress,
addressNote: item.addressNote,
latitude: item.latitude,
longitude: item.longitude,
maxGuests: item.maxGuests,
bedrooms: item.bedrooms,
beds: item.beds,
bathrooms: item.bathrooms,
hasSauna: item.hasSauna,
hasFireplace: item.hasFireplace,
hasWifi: item.hasWifi,
hasAirConditioning: item.hasAirConditioning,
petsAllowed: item.petsAllowed,
byTheLake: item.byTheLake,
evCharging: item.evCharging,
priceHintPerNightCents: item.priceHintPerNightCents,
contactName: 'Sample Host',
contactEmail: SAMPLE_EMAIL,
contactPhone: owner.phone,
published: true,
status: ListingStatus.PUBLISHED,
approvedAt: new Date(),
},
});
await prisma.listingTranslation.upsert({
where: { slug_locale: { slug: item.slug, locale: 'en' } },
create: { listingId, locale: 'en', slug: item.slug, title: item.titleEn, teaser: item.teaserEn, description: item.descEn },
update: { title: item.titleEn, teaser: item.teaserEn, description: item.descEn },
});
await prisma.listingTranslation.upsert({
where: { slug_locale: { slug: item.slug, locale: 'fi' } },
create: { listingId, locale: 'fi', slug: item.slug, title: item.titleFi, teaser: item.teaserFi, description: item.descFi },
update: { title: item.titleFi, teaser: item.teaserFi, description: item.descFi },
});
await prisma.listingImage.deleteMany({ where: { listingId } });
await prisma.listingImage.createMany({
data: [
{ listingId, url: item.cover.url, altText: item.cover.altText, order: 1, isCover: true },
...item.images.map((img, idx) => ({
listingId,
url: img.url,
altText: img.altText ?? null,
order: idx + 2,
isCover: false,
})),
],
});
console.log('Updated listing:', item.slug);
}
console.log('Seed completed for sample listings.');
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});