Initial commit: spreewaldzeit + Dockerfile for Coolify (Next.js + Prisma/SQLite)

This commit is contained in:
2026-06-03 14:08:48 +02:00
committed by Ihor_Zhekov
commit bf5d79a919
94 changed files with 12480 additions and 0 deletions

82
prisma/schema.prisma Normal file
View File

@@ -0,0 +1,82 @@
// -------------------------------------------------------------
// Spreewaldzeit Datenbankschema
// SQLite für den MVP. Das Schema ist portabel (Postgres/MySQL).
//
// Block-Modell ist bewusst generisch gehalten: `source` speichert,
// woher ein Block stammt ("manual", "airbnb", "booking", "direct").
// So kann später ein iCal-Importer die Tabelle befüllen, ohne dass
// das Schema sich ändern muss.
// -------------------------------------------------------------
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model Apartment {
id String @id @default(cuid())
slug String @unique
name String
tagline String
shortDescription String
description String
priceFrom Int // in Cent, z. B. 9500 = 95,00 €
maxGuests Int
bedrooms Int
sizeSqm Int
features String // JSON-Array als String
images String // JSON-Array URLs
airbnbUrl String?
bookingUrl String?
published Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
inquiries Inquiry[]
blocks Block[]
}
model Inquiry {
id String @id @default(cuid())
apartmentId String
apartment Apartment @relation(fields: [apartmentId], references: [id])
arrival DateTime
departure DateTime
guests Int
name String
email String
phone String?
message String
// status: new | read | confirmed | declined | archived
status String @default("new")
createdAt DateTime @default(now())
@@index([apartmentId, createdAt])
}
model Block {
id String @id @default(cuid())
apartmentId String
apartment Apartment @relation(fields: [apartmentId], references: [id])
startDate DateTime
endDate DateTime
// reason: manual | maintenance | booking
reason String @default("manual")
// source: manual | airbnb | booking | direct (für späteren iCal-Sync)
source String @default("manual")
note String?
createdAt DateTime @default(now())
@@index([apartmentId, startDate, endDate])
}
model Admin {
id String @id @default(cuid())
email String @unique
passwordHash String
createdAt DateTime @default(now())
}

141
prisma/seed.ts Normal file
View File

@@ -0,0 +1,141 @@
import { PrismaClient } from "@prisma/client";
import bcrypt from "bcryptjs";
const prisma = new PrismaClient();
async function main() {
// ------------------------------------------------------------------
// Admin-Account anlegen (aus ENV; Passwort wird gehasht gespeichert)
// ------------------------------------------------------------------
const adminEmail = process.env.ADMIN_EMAIL ?? "admin@spreewaldzeit.de";
const adminPassword = process.env.ADMIN_PASSWORD ?? "bitte-sofort-aendern";
const passwordHash = await bcrypt.hash(adminPassword, 10);
await prisma.admin.upsert({
where: { email: adminEmail },
update: { passwordHash },
create: { email: adminEmail, passwordHash },
});
console.log(`✓ Admin: ${adminEmail}`);
// ------------------------------------------------------------------
// Wohnungen
// ------------------------------------------------------------------
const apartments = [
{
slug: "kahnblick",
name: "Kahnblick",
tagline: "Wohnen direkt am Fließ",
shortDescription:
"Helles Refugium mit Blick aufs Wasser — für alle, die Ruhe, Holz und Morgennebel mögen.",
description:
"Die Ferienwohnung Kahnblick liegt am Rand von Lübbenau, nur wenige Schritte vom Hafen entfernt. Große Fenster holen das Licht des Fließes ins Zimmer, geölte Eichendielen tragen durch alle Räume. Die Küche ist vollständig ausgestattet, der private Garten lädt zum Frühstück unter alten Obstbäumen. Der Kahnhafen, von dem die traditionellen Kahnfahrten starten, ist in fünf Gehminuten erreichbar.",
priceFrom: 9500, // 95,00 €
maxGuests: 4,
bedrooms: 2,
sizeSqm: 68,
features: [
"WLAN (kostenfrei)",
"Voll ausgestattete Küche",
"Kaffeemaschine & Siebträger",
"Waschmaschine",
"Privater Garten",
"Fahrrad-Verleih vor Ort",
"Nichtraucher",
"Haustiere auf Anfrage",
],
images: [
"https://images.unsplash.com/photo-1540541338287-41700207dee6?auto=format&fit=crop&w=1800&q=80",
"https://images.unsplash.com/photo-1505693416388-ac5ce068fe85?auto=format&fit=crop&w=1800&q=80",
"https://images.unsplash.com/photo-1522771739844-6a9f6d5f14af?auto=format&fit=crop&w=1800&q=80",
"https://images.unsplash.com/photo-1600585154340-be6161a56a0c?auto=format&fit=crop&w=1800&q=80",
"https://images.unsplash.com/photo-1505692433770-36f19f51681d?auto=format&fit=crop&w=1800&q=80",
],
},
{
slug: "erlenhof",
name: "Erlenhof",
tagline: "Stille unter alten Bäumen",
shortDescription:
"Traditionelles Spreewaldhaus, behutsam renoviert — mit Kachelofen, Leseecke und tiefem Garten.",
description:
"Der Erlenhof ist ein über hundert Jahre altes Spreewaldhaus in Burg, Ortsteil Kauper. Der Kachelofen im Wohnzimmer wärmt im Winter, im Sommer zieht es Sie in den weitläufigen Garten mit Wiese, Hängematte und Feuerstelle. Zwei Schlafzimmer, ein Bad, eine offene Küche — alles in natürlichen Materialien, mit Leinen, Keramik und viel Tageslicht. Die Kahnfahrten-Anlegestelle Hauptspreewehr ist in drei Gehminuten erreicht.",
priceFrom: 11500, // 115,00 €
maxGuests: 5,
bedrooms: 2,
sizeSqm: 82,
features: [
"WLAN (kostenfrei)",
"Kachelofen",
"Voll ausgestattete Küche",
"Großer Garten mit Feuerstelle",
"Hängematte & Liegen",
"Waschmaschine & Trockner",
"Fahrradunterstand",
"Nichtraucher",
"Hund auf Anfrage willkommen",
],
images: [
"https://images.unsplash.com/photo-1600566753190-17f0baa2a6c3?auto=format&fit=crop&w=1800&q=80",
"https://images.unsplash.com/photo-1484154218962-a197022b5858?auto=format&fit=crop&w=1800&q=80",
"https://images.unsplash.com/photo-1556020685-ae41abfc9365?auto=format&fit=crop&w=1800&q=80",
"https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?auto=format&fit=crop&w=1800&q=80",
"https://images.unsplash.com/photo-1513694203232-719a280e022f?auto=format&fit=crop&w=1800&q=80",
],
},
];
for (const apt of apartments) {
await prisma.apartment.upsert({
where: { slug: apt.slug },
update: {
...apt,
features: JSON.stringify(apt.features),
images: JSON.stringify(apt.images),
},
create: {
...apt,
features: JSON.stringify(apt.features),
images: JSON.stringify(apt.images),
},
});
console.log(`✓ Wohnung: ${apt.name}`);
}
// ------------------------------------------------------------------
// Ein Beispiel-Block (belegter Zeitraum) zum Testen der Anzeige
// ------------------------------------------------------------------
const kahnblick = await prisma.apartment.findUnique({ where: { slug: "kahnblick" } });
if (kahnblick) {
const blockExists = await prisma.block.findFirst({
where: { apartmentId: kahnblick.id, reason: "manual" },
});
if (!blockExists) {
const today = new Date();
const start = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 10);
const end = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 17);
await prisma.block.create({
data: {
apartmentId: kahnblick.id,
startDate: start,
endDate: end,
reason: "manual",
source: "manual",
note: "Familientreffen der Eigentümer",
},
});
console.log("✓ Beispiel-Block: Kahnblick, +10 bis +17 Tage");
}
}
console.log("\nFertig. Admin-Login: " + adminEmail);
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});