Files
gogo-telefon/gogo/seed.py
Senad Uka 646917c322 M3: chat widget, owner dashboard, super-admin panel
- Embeddable vanilla-JS chat widget (one script tag + tenant public key):
  WebSocket, resumable sessions (24h localStorage token), honeypot + per-IP
  and per-message rate limits, Bosnian UI (§7)
- Session auth (bcrypt + signed cookies), owner accounts + super-admins,
  admin impersonation ('otvori kao salon', §11)
- Owner dashboard (§10, Bosnian): requests with same actions as email,
  call/chat history with transcripts, usage bar; settings for profile,
  working hours (manual + LLM paste-to-parse), services CRUD + 'Zalijepi
  cjenovnik' LLM import with review step, scheduling (Google OAuth free/busy
  read-only + calendar mappings + buffers, partner status), telephony
  (forwarding MMI codes per operator, ring group, worker SIP creds + QR),
  agent voice/price-mode/notes + widget snippet, notifications/SMS templates
- Super-admin panel (§11): tenant CRUD incl. plan/minutes/paid-until,
  partner API config (encrypted secrets), number/SIM registry + assignment,
  connectivity test (live availability + dry-run push), versioned prompt
  template editor with publish/rollback, playground, usage overview, health
- Fixes: WAL + busy_timeout for sqlite tests, no awaits in WS disconnect
  path (deadlock), template publish autoflush bug
- 76 tests green (incl. widget WS e2e booking flow); dashboard, widget and
  admin verified live in a browser against Postgres

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 10:51:57 +02:00

112 lines
3.7 KiB
Python

"""Seed demo data: the fictional 'Salon Merima' (Appendix A) + super-admin.
Usage: python -m gogo.seed [--partner]
"""
from __future__ import annotations
import asyncio
import sys
from sqlalchemy import select
from gogo.auth import hash_password
from gogo.crypto import encrypt
from gogo.db import Base, get_engine, get_sessionmaker
from gogo.models import Admin, ProviderConfig, Service, Tenant, User
MERIMA_HOURS = {
"mon": [["09:00", "18:00"]],
"tue": [["09:00", "18:00"]],
"wed": [["09:00", "18:00"]],
"thu": [["09:00", "18:00"]],
"fri": [["09:00", "18:00"]],
"sat": [["09:00", "14:00"]],
"sun": [],
}
MERIMA_SERVICES = [
# name, duration, price_min, price_max, home_visit, note
("Šišanje i feniranje", 45, 25, 35, False, ""),
("Farbanje cijele dužine", 90, 60, 90, False, "cijena zavisi od dužine kose"),
("Pramenovi", 120, 80, 120, False, ""),
("Keratinski tretman", 90, 70, 100, False, "za oštećenu kosu"),
("Dubinska njega", 45, 30, 40, False, "za oštećenu kosu"),
("Manikir", 45, 20, 25, False, ""),
("Gel nokti", 90, 50, 70, False, ""),
("Pedikir", 60, 30, 30, True, "moguć dolazak na kućnu adresu"),
("Depilacija nogu", 30, 15, 20, False, ""),
]
async def seed(partner: bool = False) -> None:
engine = get_engine()
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
async with get_sessionmaker()() as session:
existing = (
await session.execute(select(Tenant).where(Tenant.slug == "salon-merima"))
).scalar_one_or_none()
if existing:
print("salon-merima already seeded")
return
tenant = Tenant(
name="Salon Merima",
slug="salon-merima",
address="Ulica Veselina Masleše 12",
city="Banja Luka",
phone="+38751123456",
working_hours=MERIMA_HOURS,
scheduling_provider="partner_api" if partner else "mock",
notify_emails=["vlasnica@salon-merima.ba"],
agent_notes="Parking iza zgrade. Ne primamo djecu ispod 7 godina.",
price_mode="range",
)
session.add(tenant)
await session.flush()
for name, dur, pmin, pmax, home, note in MERIMA_SERVICES:
session.add(
Service(
tenant_id=tenant.id, name=name, duration_min=dur,
price_min=pmin, price_max=pmax, home_visit=home, agent_note=note,
)
)
if partner:
session.add(
ProviderConfig(
tenant_id=tenant.id,
provider_type="partner_api",
config={
"base_url": "http://localhost:9100",
"api_key_encrypted": encrypt("test-partner-key"),
"webhook_secret_encrypted": encrypt("test-webhook-secret"),
"catalog_sync": False,
"email_to_owner": True,
"polling_fallback": False,
},
)
)
session.add(
User(
tenant_id=tenant.id,
email="vlasnica@salon-merima.ba",
password_hash=hash_password("merima123"),
)
)
session.add(
Admin(email="admin@gogotelefon.ba", password_hash=hash_password("admin123"))
)
await session.commit()
print(f"seeded tenant {tenant.slug} ({tenant.id})")
print("owner login: vlasnica@salon-merima.ba / merima123")
print("super-admin: admin@gogotelefon.ba / admin123")
if __name__ == "__main__":
asyncio.run(seed(partner="--partner" in sys.argv))