rspace-online/server/seed-demo.ts

512 lines
17 KiB
TypeScript

/**
* Demo community seeder for the r* ecosystem
*
* Creates/ensures a "demo" community with visibility: "public" (anon read+write)
* and populates it with the "Alpine Explorer 2026" cross-app scenario.
*
* Shape IDs are deterministic so re-running is idempotent.
*/
import {
addShapes,
communityExists,
createCommunity,
getDocumentData,
loadCommunity,
} from "./community-store";
// ── Alpine Explorer 2026 — Demo Scenario ──────────────────────────
const DEMO_SHAPES: Record<string, unknown>[] = [
// ─── rTrips: Itinerary ──────────────────────────────────────
{
id: "demo-itinerary-alpine",
type: "folk-itinerary",
x: 50, y: 50, width: 400, height: 300, rotation: 0,
tripTitle: "Alpine Explorer 2026",
startDate: "2026-07-06",
endDate: "2026-07-20",
travelers: ["Maya", "Liam", "Priya", "Omar"],
items: [
{ date: "Jul 6", activity: "Fly Geneva → Chamonix shuttle", category: "travel" },
{ date: "Jul 7", activity: "Acclimatization hike — Lac Blanc", category: "hike" },
{ date: "Jul 8", activity: "Via Ferrata — Aiguille du Midi", category: "adventure" },
{ date: "Jul 9", activity: "Rest day / Chamonix town", category: "rest" },
{ date: "Jul 10", activity: "Train to Zermatt", category: "travel" },
{ date: "Jul 11", activity: "Gornergrat sunrise hike", category: "hike" },
{ date: "Jul 12", activity: "Matterhorn base camp trek", category: "hike" },
{ date: "Jul 13", activity: "Paragliding over Zermatt", category: "adventure" },
{ date: "Jul 14", activity: "Transfer to Dolomites", category: "travel" },
{ date: "Jul 15", activity: "Tre Cime di Lavaredo loop", category: "hike" },
{ date: "Jul 16", activity: "Lago di Braies kayaking", category: "adventure" },
{ date: "Jul 17", activity: "Seceda ridgeline hike", category: "hike" },
{ date: "Jul 18", activity: "Cooking class in Bolzano", category: "culture" },
{ date: "Jul 19", activity: "Free day — shopping & packing", category: "rest" },
{ date: "Jul 20", activity: "Fly home from Innsbruck", category: "travel" },
],
},
// ─── rTrips: Destinations ───────────────────────────────────
{
id: "demo-dest-chamonix",
type: "folk-destination",
x: 500, y: 50, width: 300, height: 200, rotation: 0,
destName: "Chamonix",
country: "France",
lat: 45.9237,
lng: 6.8694,
arrivalDate: "2026-07-06",
departureDate: "2026-07-10",
notes: "Base for Mont Blanc region. Book Aiguille du Midi tickets in advance.",
},
{
id: "demo-dest-zermatt",
type: "folk-destination",
x: 500, y: 280, width: 300, height: 200, rotation: 0,
destName: "Zermatt",
country: "Switzerland",
lat: 46.0207,
lng: 7.7491,
arrivalDate: "2026-07-10",
departureDate: "2026-07-14",
notes: "Car-free village. Take the Glacier Express for the scenic route.",
},
{
id: "demo-dest-dolomites",
type: "folk-destination",
x: 500, y: 510, width: 300, height: 200, rotation: 0,
destName: "Dolomites",
country: "Italy",
lat: 46.4102,
lng: 11.8440,
arrivalDate: "2026-07-14",
departureDate: "2026-07-20",
notes: "Stay in Val Gardena. Rifugio Locatelli for Tre Cime views.",
},
// ─── rNotes: Notebook ───────────────────────────────────────
{
id: "demo-notebook-trip",
type: "folk-notebook",
x: 850, y: 50, width: 300, height: 180, rotation: 0,
notebookTitle: "Alpine Explorer Planning",
description: "Shared knowledge base for our July 2026 trip across France, Switzerland, and Italy",
noteCount: 5,
collaborators: ["Maya", "Liam", "Priya", "Omar"],
},
// ─── rNotes: Notes ──────────────────────────────────────────
{
id: "demo-note-packing",
type: "folk-note",
x: 850, y: 260, width: 300, height: 250, rotation: 0,
noteTitle: "Packing Checklist",
content: "## Essential Gear\n- [ ] Hiking boots (broken in!)\n- [x] Rain jacket\n- [x] Headlamp + batteries\n- [ ] Trekking poles\n- [x] First aid kit\n- [ ] Sunscreen SPF 50\n- [ ] Water filter\n\n## Documents\n- [x] Passports\n- [ ] Travel insurance\n- [x] Hut reservations printout",
tags: ["packing", "gear", "checklist"],
editor: "Maya",
editedAt: "2 hours ago",
},
{
id: "demo-note-gear",
type: "folk-note",
x: 1180, y: 50, width: 300, height: 200, rotation: 0,
noteTitle: "Gear Research",
content: "## Via Ferrata Kit\nNeed harness + lanyard + helmet. Can rent in Chamonix for ~€25/day.\n\n## Paragliding\nTandem flights in Zermatt: ~€180pp. Book at Paragliding Zermatt (best reviews).\n\n## Camera\nBring the drone for Tre Cime. Check Italian drone regulations!",
tags: ["gear", "research", "equipment"],
editor: "Liam",
editedAt: "5 hours ago",
},
{
id: "demo-note-huts",
type: "folk-note",
x: 1180, y: 280, width: 300, height: 200, rotation: 0,
noteTitle: "Mountain Hut Reservations",
content: "## Confirmed\n- **Rifugio Locatelli** (Jul 15): 4 beds, conf #TRE2026-089\n- **Refuge du Lac Blanc** (Jul 7): 4 beds, conf #LB2026-234\n\n## Pending\n- Hörnlihütte (Matterhorn base): Waitlisted for Jul 12",
tags: ["accommodation", "reservations", "logistics"],
editor: "Priya",
editedAt: "Yesterday",
},
{
id: "demo-note-emergency",
type: "folk-note",
x: 1180, y: 510, width: 300, height: 180, rotation: 0,
noteTitle: "Emergency Contacts",
content: "## Emergency Numbers\n- **France**: 112 (EU), PGHM: +33 4 50 53 16 89\n- **Switzerland**: 1414 (REGA air rescue)\n- **Italy**: 118 (medical), 112 (general)\n\n## Insurance\nPolicy #: WA-2026-7891\nEmergency line: +1-800-555-0199",
tags: ["safety", "emergency", "contacts"],
editor: "Omar",
editedAt: "3 days ago",
},
{
id: "demo-note-photos",
type: "folk-note",
x: 850, y: 540, width: 300, height: 180, rotation: 0,
noteTitle: "Photo Spots",
content: "## Must-Capture Locations\n1. Lac Blanc reflection of Mont Blanc (sunrise)\n2. Gornergrat panorama with Matterhorn\n3. Tre Cime from Rifugio Locatelli (golden hour)\n4. Seceda ridgeline drone shots\n5. Lago di Braies turquoise water\n\nBring ND filters for long exposure water shots.",
tags: ["photography", "planning", "creative"],
editor: "Liam",
editedAt: "2 days ago",
},
// ─── rVote: Polls ───────────────────────────────────────────
{
id: "demo-poll-day5",
type: "demo-poll",
x: 50, y: 400, width: 350, height: 200, rotation: 0,
question: "Day 5 Activity — What should we do in Chamonix?",
options: [
{ label: "Via Ferrata at Aiguille du Midi", votes: 7 },
{ label: "Kayaking on Lac d'Annecy", votes: 3 },
{ label: "Rest day in town", votes: 2 },
],
totalVoters: 4,
status: "active",
endsAt: "2026-07-01",
},
{
id: "demo-poll-dinner",
type: "demo-poll",
x: 50, y: 630, width: 350, height: 200, rotation: 0,
question: "First night dinner in Zermatt?",
options: [
{ label: "Traditional fondue at Chez Vrony", votes: 5 },
{ label: "Pizza at Grampi's", votes: 4 },
{ label: "Cook at the Airbnb", votes: 1 },
],
totalVoters: 4,
status: "active",
endsAt: "2026-07-05",
},
// ─── rCart: Group Shopping Items ─────────────────────────────
{
id: "demo-cart-firstaid",
type: "demo-cart-item",
x: 50, y: 870, width: 320, height: 60, rotation: 0,
name: "Adventure First-Aid Kit",
price: 85.00,
funded: 85.00,
status: "Funded",
requestedBy: "Omar",
store: "REI",
},
{
id: "demo-cart-waterfilter",
type: "demo-cart-item",
x: 50, y: 940, width: 320, height: 60, rotation: 0,
name: "Portable Water Filter (Sawyer Squeeze)",
price: 45.00,
funded: 45.00,
status: "Funded",
requestedBy: "Maya",
store: "Amazon",
},
{
id: "demo-cart-bearcan",
type: "demo-cart-item",
x: 50, y: 1010, width: 320, height: 60, rotation: 0,
name: "Bear Canister 2x (BV500)",
price: 120.00,
funded: 80.00,
status: "In Cart",
requestedBy: "Liam",
store: "REI",
},
{
id: "demo-cart-stove",
type: "demo-cart-item",
x: 50, y: 1080, width: 320, height: 60, rotation: 0,
name: "Camp Stove + Fuel Canister",
price: 65.00,
funded: 65.00,
status: "Funded",
requestedBy: "Priya",
store: "Decathlon",
},
{
id: "demo-cart-drone",
type: "demo-cart-item",
x: 50, y: 1150, width: 320, height: 60, rotation: 0,
name: "DJI Mini 4 Pro Rental (2 weeks)",
price: 350.00,
funded: 100.00,
status: "Needs Funding",
requestedBy: "Liam",
store: "LensRentals",
},
{
id: "demo-cart-starlink",
type: "demo-cart-item",
x: 50, y: 1220, width: 320, height: 60, rotation: 0,
name: "Starlink Mini Rental (2 weeks)",
price: 200.00,
funded: 50.00,
status: "Needs Funding",
requestedBy: "Omar",
store: "StarRent.eu",
},
// ─── rFunds: Expenses ───────────────────────────────────────
{
id: "demo-expense-shuttle",
type: "demo-expense",
x: 400, y: 870, width: 320, height: 60, rotation: 0,
description: "Geneva → Chamonix shuttle (4 pax)",
amount: 186.00,
currency: "EUR",
paidBy: "Maya",
split: "equal",
category: "transport",
date: "2026-07-06",
},
{
id: "demo-expense-hut",
type: "demo-expense",
x: 400, y: 940, width: 320, height: 60, rotation: 0,
description: "Mountain hut reservations (3 nights total)",
amount: 420.00,
currency: "EUR",
paidBy: "Priya",
split: "equal",
category: "accommodation",
date: "2026-07-07",
},
{
id: "demo-expense-viaferrata",
type: "demo-expense",
x: 400, y: 1010, width: 320, height: 60, rotation: 0,
description: "Via Ferrata gear rental (4 sets)",
amount: 144.00,
currency: "EUR",
paidBy: "Liam",
split: "equal",
category: "activity",
date: "2026-07-08",
},
{
id: "demo-expense-groceries",
type: "demo-expense",
x: 400, y: 1080, width: 320, height: 60, rotation: 0,
description: "Groceries — Chamonix Carrefour",
amount: 93.00,
currency: "EUR",
paidBy: "Omar",
split: "equal",
category: "food",
date: "2026-07-06",
},
{
id: "demo-expense-paragliding",
type: "demo-expense",
x: 400, y: 1150, width: 320, height: 60, rotation: 0,
description: "Paragliding deposit (2 of 4 booked)",
amount: 360.00,
currency: "CHF",
paidBy: "Maya",
split: "custom",
category: "activity",
date: "2026-07-13",
},
// ─── rFunds: Budget ─────────────────────────────────────────
{
id: "demo-budget-trip",
type: "folk-budget",
x: 400, y: 400, width: 350, height: 250, rotation: 0,
budgetTitle: "Trip Budget",
currency: "EUR",
budgetTotal: 4000,
spent: 1203,
categories: [
{ name: "Transport", budget: 800, spent: 186 },
{ name: "Accommodation", budget: 1200, spent: 420 },
{ name: "Activities", budget: 1000, spent: 504 },
{ name: "Food", budget: 600, spent: 93 },
{ name: "Gear", budget: 400, spent: 0 },
],
},
// ─── rMaps: Location Markers ────────────────────────────────
{
id: "demo-map-chamonix",
type: "demo-map-marker",
x: 750, y: 750, width: 40, height: 40, rotation: 0,
name: "Chamonix",
lat: 45.9237,
lng: 6.8694,
emoji: "🏔️",
category: "destination",
status: "Jul 6-10",
},
{
id: "demo-map-zermatt",
type: "demo-map-marker",
x: 800, y: 750, width: 40, height: 40, rotation: 0,
name: "Zermatt",
lat: 46.0207,
lng: 7.7491,
emoji: "⛷️",
category: "destination",
status: "Jul 10-14",
},
{
id: "demo-map-dolomites",
type: "demo-map-marker",
x: 850, y: 750, width: 40, height: 40, rotation: 0,
name: "Dolomites",
lat: 46.4102,
lng: 11.8440,
emoji: "🏞️",
category: "destination",
status: "Jul 14-20",
},
{
id: "demo-map-lacblanc",
type: "demo-map-marker",
x: 900, y: 750, width: 40, height: 40, rotation: 0,
name: "Lac Blanc Hike",
lat: 45.9785,
lng: 6.8891,
emoji: "🥾",
category: "activity",
status: "Jul 7",
},
{
id: "demo-map-viaferrata",
type: "demo-map-marker",
x: 950, y: 750, width: 40, height: 40, rotation: 0,
name: "Via Ferrata",
lat: 45.8786,
lng: 6.8874,
emoji: "🧗",
category: "activity",
status: "Jul 8",
},
{
id: "demo-map-trecime",
type: "demo-map-marker",
x: 1000, y: 750, width: 40, height: 40, rotation: 0,
name: "Tre Cime Loop",
lat: 46.6190,
lng: 12.3018,
emoji: "🏔️",
category: "activity",
status: "Jul 15",
},
// ─── rNotes: Packing List (shared with rTrips) ──────────────
{
id: "demo-packing-alpine",
type: "folk-packing-list",
x: 1200, y: 750, width: 300, height: 300, rotation: 0,
listTitle: "Alpine Explorer Packing",
items: [
{ name: "Hiking boots (broken in)", packed: true, category: "footwear" },
{ name: "Rain jacket", packed: true, category: "clothing" },
{ name: "Trekking poles", packed: false, category: "gear" },
{ name: "Headlamp + batteries", packed: true, category: "gear" },
{ name: "Sunscreen SPF 50", packed: false, category: "personal" },
{ name: "Water filter", packed: false, category: "gear" },
{ name: "First aid kit", packed: true, category: "safety" },
{ name: "Passport + insurance", packed: true, category: "documents" },
],
},
// ─── rTokens: Governance Token ─────────────────────────────
{
id: "demo-mint-gov",
type: "folk-token-mint",
x: 1550, y: 50, width: 320, height: 280, rotation: 0,
tokenName: "Governance Token",
tokenSymbol: "GOV",
description: "Equal voting weight for group decisions. Each holder gets one share of governance power.",
totalSupply: 100,
issuedSupply: 100,
tokenColor: "#6d28d9",
tokenIcon: "\uD83D\uDDF3\uFE0F",
createdBy: "Maya",
createdAt: "2026-06-15T10:00:00.000Z",
},
{
id: "demo-ledger-gov",
type: "folk-token-ledger",
x: 1940, y: 50, width: 380, height: 400, rotation: 0,
mintId: "demo-mint-gov",
entries: [
{ id: "gov-1", holder: "did:key:maya", holderLabel: "Maya", amount: 25, issuedAt: "2026-06-15T10:00:00.000Z", issuedBy: "Maya", memo: "Founding member" },
{ id: "gov-2", holder: "did:key:liam", holderLabel: "Liam", amount: 25, issuedAt: "2026-06-15T10:00:00.000Z", issuedBy: "Maya", memo: "Founding member" },
{ id: "gov-3", holder: "did:key:priya", holderLabel: "Priya", amount: 25, issuedAt: "2026-06-15T10:00:00.000Z", issuedBy: "Maya", memo: "Founding member" },
{ id: "gov-4", holder: "did:key:omar", holderLabel: "Omar", amount: 25, issuedAt: "2026-06-15T10:00:00.000Z", issuedBy: "Maya", memo: "Founding member" },
],
},
{
id: "demo-arrow-gov",
type: "folk-arrow",
x: 0, y: 0, width: 0, height: 0, rotation: 0,
sourceId: "demo-mint-gov",
targetId: "demo-ledger-gov",
color: "#6d28d9",
},
// ─── rTokens: Contribution Credits ─────────────────────────
{
id: "demo-mint-cred",
type: "folk-token-mint",
x: 1550, y: 500, width: 320, height: 280, rotation: 0,
tokenName: "Contribution Credits",
tokenSymbol: "CRED",
description: "Earned through contributions to trip planning. Redeemable for activity priority picks.",
totalSupply: 500,
issuedSupply: 155,
tokenColor: "#059669",
tokenIcon: "\u2B50",
createdBy: "Maya",
createdAt: "2026-06-20T14:00:00.000Z",
},
{
id: "demo-ledger-cred",
type: "folk-token-ledger",
x: 1940, y: 500, width: 380, height: 400, rotation: 0,
mintId: "demo-mint-cred",
entries: [
{ id: "cred-1", holder: "did:key:maya", holderLabel: "Maya", amount: 50, issuedAt: "2026-06-20T14:00:00.000Z", issuedBy: "Maya", memo: "Organized itinerary" },
{ id: "cred-2", holder: "did:key:liam", holderLabel: "Liam", amount: 40, issuedAt: "2026-06-22T09:00:00.000Z", issuedBy: "Maya", memo: "Gear research & drone rental" },
{ id: "cred-3", holder: "did:key:priya", holderLabel: "Priya", amount: 35, issuedAt: "2026-06-25T11:00:00.000Z", issuedBy: "Maya", memo: "Hut reservations" },
{ id: "cred-4", holder: "omar@example.com", holderLabel: "omar", amount: 30, issuedAt: "2026-06-28T16:00:00.000Z", issuedBy: "Maya", memo: "Emergency planning (escrow)" },
],
},
{
id: "demo-arrow-cred",
type: "folk-arrow",
x: 0, y: 0, width: 0, height: 0, rotation: 0,
sourceId: "demo-mint-cred",
targetId: "demo-ledger-cred",
color: "#059669",
},
];
/**
* Ensure the demo community exists and is seeded with data.
* Called on server startup and after demo reset.
*/
export async function ensureDemoCommunity(): Promise<void> {
const exists = await communityExists("demo");
if (!exists) {
await createCommunity("r* Ecosystem Demo", "demo", null, "public");
console.log("[Demo] Created demo community with visibility: public");
} else {
await loadCommunity("demo");
}
// Check if already seeded (has shapes)
const data = getDocumentData("demo");
const shapeCount = data ? Object.keys(data.shapes || {}).length : 0;
if (shapeCount === 0) {
addShapes("demo", DEMO_SHAPES);
console.log(`[Demo] Seeded ${DEMO_SHAPES.length} shapes into demo community`);
} else {
console.log(`[Demo] Demo community already has ${shapeCount} shapes`);
}
}