266 lines
8.7 KiB
Python
266 lines
8.7 KiB
Python
"""Seed DB with admin + demo customer + categories + 12 clothing products."""
|
|
from __future__ import annotations
|
|
|
|
from core.db import SessionLocal
|
|
from core.events import event_bus
|
|
from core.security import hash_password
|
|
from core.settings_service import set_setting
|
|
|
|
from apps.auth.models import User
|
|
from apps.catalog.models import Category, Product
|
|
from apps.catalog.projector import rebuild_all
|
|
|
|
CATEGORIES = [
|
|
{"slug": "oberteile", "name": {"de": "Oberteile", "en": "Tops"}, "sort_order": 1},
|
|
{"slug": "hosen", "name": {"de": "Hosen", "en": "Pants"}, "sort_order": 2},
|
|
{"slug": "schuhe", "name": {"de": "Schuhe", "en": "Shoes"}, "sort_order": 3},
|
|
{"slug": "accessoires", "name": {"de": "Accessoires", "en": "Accessories"}, "sort_order": 4},
|
|
]
|
|
|
|
|
|
def _placeholder(color_hex: str, label: str) -> str:
|
|
# Simple SVG data URL — zero external dependencies
|
|
from urllib.parse import quote
|
|
|
|
svg = (
|
|
f"<svg xmlns='http://www.w3.org/2000/svg' width='400' height='400' viewBox='0 0 400 400'>"
|
|
f"<rect width='400' height='400' fill='#{color_hex}'/>"
|
|
f"<text x='50%' y='50%' dominant-baseline='middle' text-anchor='middle' "
|
|
f"font-family='Arial' font-size='26' fill='#ffffff'>{label}</text>"
|
|
f"</svg>"
|
|
)
|
|
return "data:image/svg+xml;utf8," + quote(svg)
|
|
|
|
|
|
PRODUCTS = [
|
|
{
|
|
"sku": "PULLI-GRUEN-M",
|
|
"name": {"de": "Grüner Kuschelpulli", "en": "Green Cozy Sweater"},
|
|
"description": {
|
|
"de": "Weicher grüner Pullover aus Bio-Baumwolle, ideal für kühle Tage.",
|
|
"en": "Soft green sweater made of organic cotton, perfect for chilly days.",
|
|
},
|
|
"price": 49.90,
|
|
"stock": 20,
|
|
"cat": "oberteile",
|
|
"color": "green",
|
|
"img": ("2e7d32", "Grüner Pulli"),
|
|
},
|
|
{
|
|
"sku": "PULLI-BLAU-L",
|
|
"name": {"de": "Blauer Rundhalspullover", "en": "Blue Crewneck Sweater"},
|
|
"description": {
|
|
"de": "Klassischer blauer Pullover mit Rundhalsausschnitt.",
|
|
"en": "Classic blue crewneck sweater.",
|
|
},
|
|
"price": 42.00,
|
|
"stock": 15,
|
|
"cat": "oberteile",
|
|
"color": "blue",
|
|
"img": ("1565c0", "Blauer Pulli"),
|
|
},
|
|
{
|
|
"sku": "TSHIRT-WHITE-M",
|
|
"name": {"de": "Weißes Basic T-Shirt", "en": "White Basic T-Shirt"},
|
|
"description": {
|
|
"de": "Leichtes Basic-Shirt aus 100% Bio-Baumwolle.",
|
|
"en": "Light basic shirt made of 100% organic cotton.",
|
|
},
|
|
"price": 14.90,
|
|
"stock": 80,
|
|
"cat": "oberteile",
|
|
"color": "white",
|
|
"img": ("f5f5f5", "Weißes Shirt"),
|
|
},
|
|
{
|
|
"sku": "TSHIRT-BLACK-L",
|
|
"name": {"de": "Schwarzes T-Shirt", "en": "Black T-Shirt"},
|
|
"description": {
|
|
"de": "Klassisches schwarzes Shirt, regular fit.",
|
|
"en": "Classic black shirt, regular fit.",
|
|
},
|
|
"price": 15.90,
|
|
"stock": 80,
|
|
"cat": "oberteile",
|
|
"color": "black",
|
|
"img": ("212121", "Schwarzes Shirt"),
|
|
},
|
|
{
|
|
"sku": "JACKE-OUTDOOR-M",
|
|
"name": {"de": "Warme Outdoor-Jacke", "en": "Warm Outdoor Jacket"},
|
|
"description": {
|
|
"de": "Wasserabweisende, wärmende Jacke zum Wandern und Radeln im Herbst.",
|
|
"en": "Water-repellent, warm jacket for hiking and cycling in autumn.",
|
|
},
|
|
"price": 129.00,
|
|
"stock": 10,
|
|
"cat": "oberteile",
|
|
"color": "olive",
|
|
"img": ("556b2f", "Outdoor-Jacke"),
|
|
},
|
|
{
|
|
"sku": "JEANS-BLAU-32",
|
|
"name": {"de": "Blaue Jeans Straight", "en": "Blue Straight Jeans"},
|
|
"description": {
|
|
"de": "Gerade geschnittene klassische Jeans in Dunkelblau.",
|
|
"en": "Straight-cut classic jeans in dark blue.",
|
|
},
|
|
"price": 69.00,
|
|
"stock": 40,
|
|
"cat": "hosen",
|
|
"color": "blue",
|
|
"img": ("0d47a1", "Blaue Jeans"),
|
|
},
|
|
{
|
|
"sku": "JEANS-SCHWARZ-34",
|
|
"name": {"de": "Schwarze Slim Jeans", "en": "Black Slim Jeans"},
|
|
"description": {
|
|
"de": "Slim-Fit-Jeans in tiefem Schwarz, leicht elastisch.",
|
|
"en": "Slim-fit jeans in deep black, slightly stretchy.",
|
|
},
|
|
"price": 74.00,
|
|
"stock": 25,
|
|
"cat": "hosen",
|
|
"color": "black",
|
|
"img": ("1b1b1b", "Schwarze Jeans"),
|
|
},
|
|
{
|
|
"sku": "HOSE-WANDER-L",
|
|
"name": {"de": "Wanderhose robust", "en": "Robust Hiking Pants"},
|
|
"description": {
|
|
"de": "Robuste Wanderhose für alle Jahreszeiten, wasserabweisend.",
|
|
"en": "Robust hiking pants for all seasons, water-repellent.",
|
|
},
|
|
"price": 89.00,
|
|
"stock": 18,
|
|
"cat": "hosen",
|
|
"color": "khaki",
|
|
"img": ("6b8e23", "Wanderhose"),
|
|
},
|
|
{
|
|
"sku": "SNEAKER-WEISS-42",
|
|
"name": {"de": "Weiße Sneaker", "en": "White Sneakers"},
|
|
"description": {
|
|
"de": "Zeitlose weiße Sneaker für den Alltag.",
|
|
"en": "Timeless white sneakers for everyday use.",
|
|
},
|
|
"price": 79.00,
|
|
"stock": 30,
|
|
"cat": "schuhe",
|
|
"color": "white",
|
|
"img": ("eeeeee", "Weiße Sneaker"),
|
|
},
|
|
{
|
|
"sku": "WANDERSCHUH-43",
|
|
"name": {"de": "Wanderschuhe warm", "en": "Warm Hiking Boots"},
|
|
"description": {
|
|
"de": "Warme Wanderschuhe mit guter Dämpfung und rutschfester Sohle.",
|
|
"en": "Warm hiking boots with great cushioning and slip-resistant sole.",
|
|
},
|
|
"price": 149.00,
|
|
"stock": 12,
|
|
"cat": "schuhe",
|
|
"color": "brown",
|
|
"img": ("6d4c41", "Wanderschuhe"),
|
|
},
|
|
{
|
|
"sku": "MUETZE-WOLLE",
|
|
"name": {"de": "Wollmütze grün", "en": "Green Wool Beanie"},
|
|
"description": {
|
|
"de": "Warme grüne Wollmütze für kalte Tage.",
|
|
"en": "Warm green wool beanie for cold days.",
|
|
},
|
|
"price": 19.90,
|
|
"stock": 50,
|
|
"cat": "accessoires",
|
|
"color": "green",
|
|
"img": ("388e3c", "Mütze"),
|
|
},
|
|
{
|
|
"sku": "SCHAL-GRAU",
|
|
"name": {"de": "Grauer Schal", "en": "Grey Scarf"},
|
|
"description": {
|
|
"de": "Weicher grauer Schal aus Merinowolle.",
|
|
"en": "Soft grey scarf made of merino wool.",
|
|
},
|
|
"price": 29.00,
|
|
"stock": 35,
|
|
"cat": "accessoires",
|
|
"color": "grey",
|
|
"img": ("757575", "Grauer Schal"),
|
|
},
|
|
]
|
|
|
|
|
|
def seed() -> None:
|
|
db = SessionLocal()
|
|
try:
|
|
# Shop name setting
|
|
if not db.query(User).filter_by(email="admin@example.com").first():
|
|
set_setting(db, "core.shop_name", "Demo Shop")
|
|
|
|
# Users
|
|
if not db.query(User).filter_by(email="admin@example.com").first():
|
|
admin = User(
|
|
email="admin@example.com",
|
|
password_hash=hash_password("admin123"),
|
|
role="admin",
|
|
name="Admin",
|
|
locale="de",
|
|
)
|
|
db.add(admin)
|
|
if not db.query(User).filter_by(email="kunde@example.com").first():
|
|
customer = User(
|
|
email="kunde@example.com",
|
|
password_hash=hash_password("kunde123"),
|
|
role="customer",
|
|
name="Demo Kunde",
|
|
locale="de",
|
|
)
|
|
db.add(customer)
|
|
db.commit()
|
|
|
|
# Categories
|
|
cats_by_slug: dict[str, Category] = {}
|
|
for c in CATEGORIES:
|
|
row = db.query(Category).filter_by(slug=c["slug"]).first()
|
|
if not row:
|
|
row = Category(slug=c["slug"], name=c["name"], sort_order=c["sort_order"])
|
|
db.add(row)
|
|
db.commit()
|
|
db.refresh(row)
|
|
event_bus.publish("category.created", {"id": row.id}, db=db)
|
|
cats_by_slug[c["slug"]] = row
|
|
|
|
# Products
|
|
for pd in PRODUCTS:
|
|
if db.query(Product).filter_by(sku=pd["sku"]).first():
|
|
continue
|
|
image = _placeholder(*pd["img"])
|
|
p = Product(
|
|
sku=pd["sku"],
|
|
name=pd["name"],
|
|
description=pd["description"],
|
|
price=pd["price"],
|
|
currency="EUR",
|
|
stock=pd["stock"],
|
|
active=True,
|
|
image_url=image,
|
|
category_id=cats_by_slug[pd["cat"]].id,
|
|
attributes={"color": pd["color"]},
|
|
)
|
|
db.add(p)
|
|
db.commit()
|
|
db.refresh(p)
|
|
event_bus.publish("product.created", {"id": p.id, "sku": p.sku}, db=db)
|
|
|
|
# Rebuild Redis cache (idempotent)
|
|
rebuild_all(db)
|
|
print("Seed complete.")
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
seed()
|