873 lines
28 KiB
JavaScript
873 lines
28 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";
|
|
const SAMPLE_IMAGE_DIR = path.join(__dirname, "..", "sampleimages");
|
|
|
|
function detectMimeType(fileName) {
|
|
const ext = path.extname(fileName).toLowerCase();
|
|
if (ext === ".png") return "image/png";
|
|
if (ext === ".webp") return "image/webp";
|
|
if (ext === ".gif") return "image/gif";
|
|
return "image/jpeg";
|
|
}
|
|
|
|
function loadSampleImage(fileName) {
|
|
if (!fileName) return null;
|
|
const filePath = path.join(SAMPLE_IMAGE_DIR, fileName);
|
|
if (!fs.existsSync(filePath)) {
|
|
console.warn(`Sample image missing: ${filePath}`);
|
|
return null;
|
|
}
|
|
const data = fs.readFileSync(filePath);
|
|
return {
|
|
data,
|
|
size: data.length,
|
|
mimeType: detectMimeType(fileName),
|
|
};
|
|
}
|
|
|
|
async function main() {
|
|
const adminEmail = process.env.ADMIN_EMAIL;
|
|
const adminPassword = process.env.ADMIN_INITIAL_PASSWORD;
|
|
function buildImageCreates(item) {
|
|
const results = [];
|
|
const coverFile = loadSampleImage(item.cover.file);
|
|
if (coverFile || item.cover.url) {
|
|
results.push({
|
|
data: coverFile?.data,
|
|
mimeType: coverFile?.mimeType || (item.cover.url ? null : undefined),
|
|
size: coverFile?.size ?? null,
|
|
url: coverFile ? null : (item.cover.url ?? null),
|
|
altText: item.cover.altText ?? null,
|
|
order: 1,
|
|
isCover: true,
|
|
});
|
|
}
|
|
|
|
item.images.forEach((img) => {
|
|
const file = loadSampleImage(img.file);
|
|
const hasSource = file || img.url;
|
|
if (!hasSource) return;
|
|
results.push({
|
|
data: file?.data,
|
|
mimeType: file?.mimeType || (img.url ? null : undefined),
|
|
size: file?.size ?? null,
|
|
url: file ? null : (img.url ?? null),
|
|
altText: img.altText ?? null,
|
|
order: results.length + 1,
|
|
isCover: false,
|
|
});
|
|
});
|
|
|
|
return results;
|
|
}
|
|
|
|
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(),
|
|
},
|
|
});
|
|
|
|
let listings = [
|
|
{
|
|
slug: SAMPLE_SLUG,
|
|
isSample: true,
|
|
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,
|
|
hasKitchen: true,
|
|
hasDishwasher: false,
|
|
hasWashingMachine: false,
|
|
hasBarbecue: false,
|
|
hasMicrowave: true,
|
|
hasFreeParking: true,
|
|
petsAllowed: false,
|
|
byTheLake: true,
|
|
evChargingAvailable: true,
|
|
hasSkiPass: false,
|
|
priceWeekdayEuros: 145,
|
|
priceWeekendEuros: 165,
|
|
cover: {
|
|
file: "saimaa-lakeside-cabin-cover.jpg",
|
|
url: "https://images.unsplash.com/photo-1505691938895-1758d7feb511?auto=format&fit=crop&w=1600&q=80",
|
|
altText: "Lakeside cabin with sauna",
|
|
},
|
|
images: [
|
|
{
|
|
file: "saimaa-lakeside-cabin-sauna.jpg",
|
|
url: "https://images.unsplash.com/photo-1505693416388-ac5ce068fe85?auto=format&fit=crop&w=1600&q=80",
|
|
altText: "Wood-fired sauna by the lake",
|
|
},
|
|
{
|
|
file: "saimaa-lakeside-cabin-lounge.jpg",
|
|
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",
|
|
isSample: true,
|
|
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,
|
|
hasKitchen: true,
|
|
hasDishwasher: false,
|
|
hasWashingMachine: false,
|
|
hasBarbecue: false,
|
|
hasMicrowave: true,
|
|
hasFreeParking: false,
|
|
petsAllowed: false,
|
|
byTheLake: false,
|
|
evChargingAvailable: true,
|
|
hasSkiPass: false,
|
|
priceWeekdayEuros: 165,
|
|
priceWeekendEuros: 185,
|
|
cover: {
|
|
file: "helsinki-design-loft-cover.jpg",
|
|
url: "https://images.unsplash.com/photo-1505693415763-3bd1620f58c3?auto=format&fit=crop&w=1600&q=80",
|
|
altText: "Modern loft living room",
|
|
},
|
|
images: [
|
|
{
|
|
file: "helsinki-design-loft-balcony.jpg",
|
|
url: "https://images.unsplash.com/photo-1505691938895-1758d7feb511?auto=format&fit=crop&w=1600&q=80",
|
|
altText: "Balcony view",
|
|
},
|
|
{
|
|
file: "helsinki-design-loft-bedroom.jpg",
|
|
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",
|
|
isSample: true,
|
|
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,
|
|
hasKitchen: true,
|
|
hasDishwasher: false,
|
|
hasWashingMachine: false,
|
|
hasBarbecue: false,
|
|
hasMicrowave: true,
|
|
hasFreeParking: true,
|
|
petsAllowed: true,
|
|
byTheLake: false,
|
|
evChargingAvailable: false,
|
|
hasSkiPass: false,
|
|
priceWeekdayEuros: 110,
|
|
priceWeekendEuros: 125,
|
|
cover: {
|
|
file: "turku-riverside-apartment-cover.jpg",
|
|
url: "https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?auto=format&fit=crop&w=1600&q=80",
|
|
altText: "Apartment living room",
|
|
},
|
|
images: [
|
|
{
|
|
file: "turku-riverside-apartment-kitchen.jpg",
|
|
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",
|
|
isSample: true,
|
|
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,
|
|
hasKitchen: true,
|
|
hasDishwasher: false,
|
|
hasWashingMachine: false,
|
|
hasBarbecue: false,
|
|
hasMicrowave: false,
|
|
hasFreeParking: true,
|
|
petsAllowed: false,
|
|
byTheLake: true,
|
|
evChargingAvailable: true,
|
|
evChargingOnSite: true,
|
|
hasSkiPass: true,
|
|
priceWeekdayEuros: 189,
|
|
priceWeekendEuros: 215,
|
|
cover: {
|
|
file: "rovaniemi-aurora-cabin-cover.jpg",
|
|
url: "https://images.unsplash.com/photo-1505693416388-ac5ce068fe85?auto=format&fit=crop&w=1600&q=80",
|
|
altText: "Aurora cabin by the river",
|
|
},
|
|
images: [
|
|
{
|
|
file: "rovaniemi-aurora-cabin-lounge.jpg",
|
|
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",
|
|
isSample: true,
|
|
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,
|
|
hasKitchen: true,
|
|
hasDishwasher: false,
|
|
hasWashingMachine: false,
|
|
hasBarbecue: false,
|
|
hasMicrowave: true,
|
|
hasFreeParking: false,
|
|
petsAllowed: false,
|
|
byTheLake: false,
|
|
evChargingAvailable: false,
|
|
hasSkiPass: false,
|
|
priceWeekdayEuros: 95,
|
|
priceWeekendEuros: 110,
|
|
cover: {
|
|
file: "tampere-sauna-studio-cover.jpg",
|
|
url: "https://images.unsplash.com/photo-1505691938895-1758d7feb511?auto=format&fit=crop&w=1600&q=80",
|
|
altText: "Studio interior",
|
|
},
|
|
images: [
|
|
{
|
|
file: "tampere-sauna-studio-sauna.jpg",
|
|
url: "https://images.unsplash.com/photo-1505691938895-1758d7feb511?auto=format&fit=crop&w=1600&q=80",
|
|
altText: "Private sauna",
|
|
},
|
|
],
|
|
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",
|
|
isSample: true,
|
|
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,
|
|
hasKitchen: true,
|
|
hasDishwasher: false,
|
|
hasWashingMachine: false,
|
|
hasBarbecue: false,
|
|
hasMicrowave: true,
|
|
hasFreeParking: true,
|
|
petsAllowed: true,
|
|
byTheLake: true,
|
|
evChargingAvailable: true,
|
|
evChargingOnSite: true,
|
|
hasSkiPass: true,
|
|
priceWeekdayEuros: 245,
|
|
priceWeekendEuros: 275,
|
|
cover: {
|
|
file: "vaasa-seaside-villa-cover.jpg",
|
|
url: "https://images.unsplash.com/photo-1505691938895-1758d7feb511?auto=format&fit=crop&w=1600&q=80",
|
|
altText: "Seaside villa deck",
|
|
},
|
|
images: [
|
|
{
|
|
file: "vaasa-seaside-villa-lounge.jpg",
|
|
url: "https://images.unsplash.com/photo-1505691938895-1758d7feb511?auto=format&fit=crop&w=1600&q=80",
|
|
altText: "Seaside villa lounge",
|
|
},
|
|
],
|
|
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",
|
|
isSample: true,
|
|
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,
|
|
hasKitchen: true,
|
|
hasDishwasher: false,
|
|
hasWashingMachine: false,
|
|
hasBarbecue: false,
|
|
hasMicrowave: true,
|
|
hasFreeParking: true,
|
|
petsAllowed: false,
|
|
byTheLake: true,
|
|
evChargingAvailable: true,
|
|
evChargingOnSite: true,
|
|
hasSkiPass: true,
|
|
priceWeekdayEuros: 129,
|
|
priceWeekendEuros: 149,
|
|
cover: {
|
|
file: "kuopio-lakeside-apartment-cover.jpg",
|
|
url: "https://images.unsplash.com/photo-1470246973918-29a93221c455?auto=format&fit=crop&w=1600&q=80",
|
|
altText: "Lake view balcony",
|
|
},
|
|
images: [
|
|
{
|
|
file: "kuopio-lakeside-apartment-sauna.jpg",
|
|
url: "https://images.unsplash.com/photo-1470246973918-29a93221c455?auto=format&fit=crop&w=1600&q=80",
|
|
altText: "Apartment sauna",
|
|
},
|
|
],
|
|
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",
|
|
isSample: true,
|
|
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,
|
|
hasKitchen: true,
|
|
hasDishwasher: false,
|
|
hasWashingMachine: false,
|
|
hasBarbecue: false,
|
|
hasMicrowave: true,
|
|
hasFreeParking: false,
|
|
petsAllowed: false,
|
|
byTheLake: false,
|
|
evChargingAvailable: false,
|
|
hasSkiPass: false,
|
|
priceWeekdayEuros: 99,
|
|
priceWeekendEuros: 115,
|
|
cover: {
|
|
file: "porvoo-river-loft-cover.jpg",
|
|
url: "https://images.unsplash.com/photo-1505691938895-1758d7feb511?auto=format&fit=crop&w=1600&q=80",
|
|
altText: "Loft interior",
|
|
},
|
|
images: [
|
|
{
|
|
file: "porvoo-river-loft-fireplace.jpg",
|
|
url: "https://images.unsplash.com/photo-1505691938895-1758d7feb511?auto=format&fit=crop&w=1600&q=80",
|
|
altText: "Fireplace corner",
|
|
},
|
|
],
|
|
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",
|
|
isSample: true,
|
|
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,
|
|
hasKitchen: true,
|
|
hasDishwasher: false,
|
|
hasWashingMachine: false,
|
|
hasBarbecue: false,
|
|
hasMicrowave: true,
|
|
hasFreeParking: true,
|
|
petsAllowed: false,
|
|
byTheLake: false,
|
|
evChargingAvailable: true,
|
|
evChargingOnSite: true,
|
|
priceWeekdayEuros: 105,
|
|
priceWeekendEuros: 120,
|
|
cover: {
|
|
file: "oulu-tech-apartment-cover.jpg",
|
|
url: "https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?auto=format&fit=crop&w=1600&q=80",
|
|
altText: "Modern apartment",
|
|
},
|
|
images: [
|
|
{
|
|
file: "oulu-tech-apartment-desk.jpg",
|
|
url: "https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?auto=format&fit=crop&w=1600&q=80",
|
|
altText: "Work desk in apartment",
|
|
},
|
|
],
|
|
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",
|
|
isSample: true,
|
|
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,
|
|
hasKitchen: true,
|
|
hasDishwasher: false,
|
|
hasWashingMachine: false,
|
|
hasBarbecue: false,
|
|
hasMicrowave: true,
|
|
hasFreeParking: true,
|
|
petsAllowed: false,
|
|
byTheLake: true,
|
|
evChargingAvailable: true,
|
|
hasSkiPass: true,
|
|
priceWeekdayEuros: 115,
|
|
priceWeekendEuros: 130,
|
|
cover: {
|
|
file: "mariehamn-harbor-flat-cover.jpg",
|
|
url: "https://images.unsplash.com/photo-1470246973918-29a93221c455?auto=format&fit=crop&w=1600&q=80",
|
|
altText: "Harbor view",
|
|
},
|
|
images: [
|
|
{
|
|
file: "mariehamn-harbor-flat-living.jpg",
|
|
url: "https://images.unsplash.com/photo-1470246973918-29a93221c455?auto=format&fit=crop&w=1600&q=80",
|
|
altText: "Harbor-facing living room",
|
|
},
|
|
],
|
|
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.",
|
|
},
|
|
];
|
|
|
|
// Fill in any missing amenities/prices with reasonable defaults
|
|
const randBool = (p = 0.5) => Math.random() < p;
|
|
listings = listings.map((item) => {
|
|
const weekdayPrice =
|
|
item.priceWeekdayEuros ?? Math.round(Math.random() * (220 - 90) + 90);
|
|
const evChargingOnSite = item.evChargingOnSite ?? randBool(0.15);
|
|
const evChargingAvailable =
|
|
item.evChargingAvailable ?? evChargingOnSite ?? randBool(0.4);
|
|
return {
|
|
...item,
|
|
priceWeekdayEuros: weekdayPrice,
|
|
priceWeekendEuros:
|
|
item.priceWeekendEuros ?? (weekdayPrice ? weekdayPrice + 15 : null),
|
|
hasKitchen: item.hasKitchen ?? randBool(0.9),
|
|
hasDishwasher: item.hasDishwasher ?? randBool(0.6),
|
|
hasWashingMachine: item.hasWashingMachine ?? randBool(0.6),
|
|
hasBarbecue: item.hasBarbecue ?? randBool(0.5),
|
|
hasMicrowave: item.hasMicrowave ?? randBool(0.7),
|
|
hasFreeParking: item.hasFreeParking ?? randBool(0.6),
|
|
evChargingOnSite,
|
|
evChargingAvailable,
|
|
wheelchairAccessible: item.wheelchairAccessible ?? randBool(0.25),
|
|
hasSkiPass: item.hasSkiPass ?? randBool(0.2),
|
|
};
|
|
});
|
|
|
|
for (const item of listings) {
|
|
const existing = await prisma.listingTranslation.findFirst({
|
|
where: { slug: item.slug },
|
|
select: { listingId: true },
|
|
});
|
|
const imageCreates = buildImageCreates(item);
|
|
if (!existing) {
|
|
const created = await prisma.listing.create({
|
|
data: {
|
|
ownerId: owner.id,
|
|
status: ListingStatus.PUBLISHED,
|
|
approvedAt: new Date(),
|
|
approvedById: adminUser ? adminUser.id : owner.id,
|
|
isSample: item.isSample ?? false,
|
|
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,
|
|
hasKitchen: item.hasKitchen ?? false,
|
|
hasDishwasher: item.hasDishwasher ?? false,
|
|
hasWashingMachine: item.hasWashingMachine ?? false,
|
|
hasBarbecue: item.hasBarbecue ?? false,
|
|
hasMicrowave: item.hasMicrowave ?? false,
|
|
hasFreeParking: item.hasFreeParking ?? false,
|
|
petsAllowed: item.petsAllowed,
|
|
byTheLake: item.byTheLake,
|
|
evChargingAvailable:
|
|
item.evChargingAvailable ?? item.evChargingOnSite ?? false,
|
|
evChargingOnSite: item.evChargingOnSite ?? false,
|
|
wheelchairAccessible: item.wheelchairAccessible ?? false,
|
|
priceWeekdayEuros: item.priceWeekdayEuros,
|
|
priceWeekendEuros: item.priceWeekendEuros,
|
|
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: imageCreates.length ? { create: imageCreates } : undefined,
|
|
},
|
|
});
|
|
console.log("Seeded listing:", created.id, item.slug);
|
|
continue;
|
|
}
|
|
|
|
const listingId = existing.listingId;
|
|
await prisma.listing.update({
|
|
where: { id: listingId },
|
|
data: {
|
|
isSample: item.isSample ?? false,
|
|
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,
|
|
hasKitchen: item.hasKitchen ?? false,
|
|
hasDishwasher: item.hasDishwasher ?? false,
|
|
hasWashingMachine: item.hasWashingMachine ?? false,
|
|
hasBarbecue: item.hasBarbecue ?? false,
|
|
hasMicrowave: item.hasMicrowave ?? false,
|
|
hasFreeParking: item.hasFreeParking ?? false,
|
|
petsAllowed: item.petsAllowed,
|
|
byTheLake: item.byTheLake,
|
|
evChargingAvailable:
|
|
item.evChargingAvailable ?? item.evChargingOnSite ?? false,
|
|
evChargingOnSite: item.evChargingOnSite ?? false,
|
|
wheelchairAccessible: item.wheelchairAccessible ?? false,
|
|
priceWeekdayEuros: item.priceWeekdayEuros,
|
|
priceWeekendEuros: item.priceWeekendEuros,
|
|
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 } });
|
|
if (imageCreates.length) {
|
|
await prisma.listingImage.createMany({
|
|
data: imageCreates.map((img) => ({
|
|
...img,
|
|
listingId,
|
|
})),
|
|
});
|
|
}
|
|
|
|
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();
|
|
});
|