wwwlayermeshusa/seedv2.mjs
2026-03-10 09:54:58 +01:00

529 lines
21 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/*
* LM USA — Seed Script
*
* Usage:
* PAYLOAD_URL=http://localhost:3000 PAYLOAD_API_KEY=your-key node seed.mjs
*
* Creates:
* - Contact form
* - Home page /
* - Virtuell Fiber /virtuell-fiber
* - VPS Server /vps
* - Contact /contact
* - Header navigation
* - Footer
*/
const PAYLOAD_URL = process.env.PAYLOAD_URL || 'http://localhost:3000'
const API_KEY = process.env.PAYLOAD_API_KEY
if (!API_KEY) {
console.error('❌ Missing PAYLOAD_API_KEY environment variable')
process.exit(1)
}
const headers = {
'Content-Type': 'application/json',
Authorization: `users API-Key ${API_KEY}`,
}
// ─── Helpers ──────────────────────────────────────────────────────────────────
function richText(text) {
const paragraphs = text.split('\n').filter(Boolean)
return {
root: {
type: 'root',
children: paragraphs.map((p) => ({
type: 'paragraph',
children: [
{ type: 'text', text: p, format: 0, detail: 0, mode: 'normal', style: '', version: 1 },
],
direction: 'ltr',
format: '',
indent: 0,
version: 1,
})),
direction: 'ltr',
format: '',
indent: 0,
version: 1,
},
}
}
async function apiPost(collection, data) {
const res = await fetch(`${PAYLOAD_URL}/api/${collection}`, {
method: 'POST',
headers,
body: JSON.stringify(data),
})
if (!res.ok) {
const err = await res.text()
throw new Error(`POST /api/${collection} failed (${res.status}): ${err}`)
}
return res.json()
}
async function apiGet(collection, query = '') {
const res = await fetch(`${PAYLOAD_URL}/api/${collection}${query}`, { headers })
if (!res.ok) {
const err = await res.text()
throw new Error(`GET /api/${collection} failed (${res.status}): ${err}`)
}
return res.json()
}
async function apiUpdateGlobal(slug, data) {
const res = await fetch(`${PAYLOAD_URL}/api/globals/${slug}`, {
method: 'POST',
headers,
body: JSON.stringify(data),
})
if (!res.ok) {
const err = await res.text()
throw new Error(`POST /api/globals/${slug} failed (${res.status}): ${err}`)
}
return res.json()
}
async function createPageIfNotExists(slug, data) {
const existing = await apiGet('pages', `?where[slug][equals]=${slug}&limit=1`)
if (existing.docs?.length > 0) {
console.log(` ⏭ Page "${slug}" already exists — skipping`)
return existing.docs[0]
}
const result = await apiPost('pages', data)
return result.doc
}
// ─── Main ─────────────────────────────────────────────────────────────────────
async function seed() {
console.log(`🌱 Seeding LM USA database at ${PAYLOAD_URL}...`)
// ─── 1. Contact Form ───────────────────────────────────────────────────────
console.log(' → Creating contact form...')
let contactForm
const existingForms = await apiGet('forms', '?where[title][equals]=Contact Form&limit=1')
if (existingForms.docs?.length > 0) {
console.log(' ⏭ Contact form already exists — skipping')
contactForm = existingForms.docs[0]
} else {
const result = await apiPost('forms', {
title: 'Contact Form',
fields: [
{ name: 'name', label: 'Name', blockType: 'text', required: true, width: 50 },
{ name: 'company', label: 'Company', blockType: 'text', required: false, width: 50 },
{ name: 'email', label: 'Email', blockType: 'email', required: true, width: 50 },
{ name: 'phone', label: 'Phone', blockType: 'text', required: false, width: 50 },
{ name: 'message', label: 'Message', blockType: 'textarea', required: true },
],
submitButtonLabel: 'Send Message',
confirmationType: 'message',
confirmationMessage: richText(
"Thanks for reaching out. We'll get back to you within 24 hours.",
),
})
contactForm = result.doc
}
// ─── 2. Home Page ──────────────────────────────────────────────────────────
console.log(' → Creating Home page...')
await createPageIfNotExists('index', {
title: 'Home',
slug: 'index',
_status: 'published',
publishedAt: new Date().toISOString(),
layout: [
// Hero
// theme: 'light' | 'dark'
// textColor: 'auto' | 'white' | 'dark'
{
blockType: 'lmHero',
heading: 'Infrastructure Without Borders',
subheading: 'Layer & Mesh USA',
body: 'Edge compute, virtual fiber, and managed servers — built for businesses that need sovereign, reliable infrastructure. Configure your setup and get started in minutes.',
ctaText: 'Explore Products',
ctaLink: '#products',
secondaryCtaText: 'Contact Us',
secondaryCtaLink: '/contact',
theme: 'dark',
textColor: 'auto',
},
// Link Cards
// cardStyle: 'outlined' | 'dark' | 'gray' | 'yellow'
// sectionBackground: 'white' | 'gray' | 'dark'
// title is textarea (supports \n)
{
blockType: 'lmLinkCards',
heading: 'Choose Your Infrastructure',
description: 'Two paths to managed IT — hardware at your location or virtual in our cloud.',
columns: '2',
cardStyle: 'outlined',
sectionBackground: 'dark',
anchorId: 'products',
cards: [
{
title: 'Virtuell Fiber\nEdge compute hardware deployed at your location. Plug in, connect to the mesh — instant private infrastructure.',
linkLabel: 'Explore VF Enhet',
linkUrl: '/virtuell-fiber',
},
{
title: 'VPS Servers\nCloud-hosted virtual servers on Layer & Mesh infrastructure. Full root access, instant provisioning, sovereign data.',
linkLabel: 'Configure a VPS',
linkUrl: '/vps',
},
],
},
// Statistics
// numberColor: 'gradient' | 'yellow' | 'mint' | 'dark' | 'white'
// sectionBackground: 'white' | 'dark' | 'gray'
{
blockType: 'lmStatistics',
heading: 'Built for Reliability',
sectionBackground: 'dark',
numberColor: 'gradient',
stats: [
{ number: '99.99%', label: 'Uptime SLA' },
{ number: '<5ms', label: 'Edge Latency' },
{ number: '24/7', label: 'Support' },
{ number: '100%', label: 'Swedish Sovereign' },
],
},
// Swedish CTA
// sectionBackground: 'dark' | 'elevated' | 'gray'
{
blockType: 'lmSwedishCta',
headingText: 'Söker du Fiber Direkt i Sverige?',
subText: 'Visit our Swedish site for local fiber and hosting services.',
ctaLabel: 'fiberdirekt.se',
ctaUrl: 'https://fiberdirekt.se',
sectionBackground: 'dark',
},
// Bottom CTA
// sectionBackground: 'yellow' | 'dark' | 'gray' | 'white'
// alignment: 'center' | 'left'
// size: 'small' | 'medium' | 'large'
{
blockType: 'lmCtaBanner',
heading: 'Ready to get started?',
subheading: 'Configure your infrastructure or talk to our team — no commitment required.',
ctaText: 'Contact Us',
ctaLink: '/contact',
secondaryCtaText: 'View VPS Pricing',
secondaryCtaLink: '/vps',
sectionBackground: 'yellow',
alignment: 'center',
size: 'medium',
},
],
})
// ─── 3. Virtuell Fiber Page ────────────────────────────────────────────────
console.log(' → Creating Virtuell Fiber page...')
await createPageIfNotExists('virtuell-fiber', {
title: 'Virtuell Fiber',
slug: 'virtuell-fiber',
_status: 'published',
publishedAt: new Date().toISOString(),
layout: [
// Product Detail — eyebrow as breadcrumb, specs in richText body (right col)
// sectionBackground: 'white' | 'dark'
// cartProductType: 'hardware' | 'vps' | 'service'
{
blockType: 'lmProductDetail',
eyebrow: 'Platform / Network / Virtual Fiber',
productName: 'VF Standard',
description: 'Virtual Fiber is not a VPN. It creates an encrypted, transparent Layer 2 network, built on top of any available internet connection, that behaves exactly like a private fiber link between your locations. The VF Standard unit ships pre-configured and ready. Connect it to your existing broadband, fiber, or 4G connection and it joins the Layer & Mesh mesh automatically. Your devices get full Layer 2 transport to every other point on the network — with VLAN, QinQ, and Full MTU support — without a single line of configuration. No fixed IP address needed. No IT team required on site. Works as a permanent connection or deployed temporarily. Contract terms start at one day.',
body: richText('$90 / month\nShipping additional. No setup fee. Cancel anytime.\nPerformance\nThroughput: Up to 1,000 Mbit/s\nTransport: Encrypted Layer 2 over any internet\nRedundancy: Automatic failover via 4G or secondary ISP\nHardware\nForm factor: 1U rack-mounted\nCooling: Active (fan)\nWAN: 2 × 1GbE RJ-45\nLAN: 2 × 1GbE RJ-45\nFeatures\nPlug and play — pre-configured at dispatch\nVLAN · QinQ · Full MTU\nNo public IP address required\nWorld-class Layer 2 encryption'),
ctaText: 'Order Now — $90/month',
ctaLink: '/contact',
secondaryCtaText: 'Download Datasheet',
secondaryCtaLink: '#',
enableCart: true,
cartProductName: 'VF Standard',
cartPrice: 90,
cartProductType: 'hardware',
sectionBackground: 'dark',
},
// Service Cards — "What Virtual Fiber Makes Possible"
// cardSurface: 'outlined-teal' | 'dark' | 'elevated' | 'teal' | 'light' | 'cyan'
// sectionBackground: 'dark' | 'gray' | 'white' | 'teal'
// iconSlug: 'backup' | 'colocation' | 'disaster-recovery' | 'connectivity' | 'connectivity-grid' | 'storage' | 'virtual-server'
{
blockType: 'lmServiceCards',
heading: 'What Virtual Fiber Makes Possible',
columns: '3',
cardSurface: 'outlined-teal',
sectionBackground: 'dark',
cards: [
{
eyebrow: 'Reach',
title: 'Access Resources From Anywhere',
description: 'Your team in New York reaches your Stockholm servers the same way they reach the printer down the hall — over a private Layer 2 network, without a VPN client or public IP.',
iconSlug: 'connectivity',
},
{
eyebrow: 'Simplicity',
title: 'No Configuration Required',
description: 'Units arrive ready. Connect to any internet access — fiber, broadband, 4G — and the device joins the mesh automatically. Dual transport paths for redundancy are supported out of the box.',
iconSlug: 'connectivity-grid',
},
{
eyebrow: 'Security',
title: 'World-Class Layer 2 Encryption',
description: 'All traffic is encrypted using state-of-the-art technology. Secure channels are established over public networks without any configuration — a clear replacement for traditional VPN.',
iconSlug: 'storage',
},
],
},
// USP Checklist — "VF Standard at a Glance"
// checkColor: 'dark' | 'yellow' | 'gray'
// sectionBackground: 'white' | 'gray' | 'dark'
// textColor: 'dark' | 'white'
{
blockType: 'lmUspChecklist',
heading: 'VF Standard at a Glance',
sectionBackground: 'dark',
textColor: 'white',
checkColor: 'dark',
items: [
{ text: 'Micro PC portable unit, ships pre-configured, online in minutes' },
{ text: 'Up to 1,000 Mbit/s Layer 2 encrypted transport, not a VPN, a dedicated private network' },
{ text: '2 WAN + 2 LAN ports (RJ-45, 1GbE), or failover over 4G/5G or secondary ISP' },
{ text: 'Full VLAN, QinQ, and MTU support, works with all Layer & Mesh services natively' },
{ text: 'No public IP address needed, works over any available internet connection' },
{ text: '$90/month subscription, no long-term commitment required, shipping additional' },
],
},
// FAQ — matching VF-specific questions from screenshot
// theme: 'gray' | 'light' | 'dark'
{
blockType: 'lmFaq',
heading: 'Virtual Fiber Questions',
theme: 'dark',
items: [
{
question: "What's the difference between Virtual Fiber and a VPN?",
answer: richText(
'Virtual Fiber creates a true Layer 2 network — your devices see each other as if they were on the same physical switch. A VPN tunnels Layer 3 traffic and requires client software, configuration, and a public IP. Virtual Fiber needs none of that.',
),
},
{
question: 'What internet connection does the VF Standard need?',
answer: richText(
'Any internet connection works — broadband, fiber, 4G/5G, or even satellite. The VF Standard automatically establishes encrypted transport over whatever is available. For redundancy, connect a second WAN port to a backup ISP or 4G modem.',
),
},
{
question: 'Does it need any configuration on site?',
answer: richText(
'No. Units are fully configured before dispatch. Connect the WAN port to your internet access and the LAN ports to your network — the unit joins the Layer & Mesh mesh automatically. There is nothing to configure.',
),
},
{
question: 'Can I connect multiple offices?',
answer: richText(
'Yes. Each office gets a VF Standard unit. All units on your account see each other over a single flat Layer 2 network. Add or remove locations at any time — no reconfiguration needed.',
),
},
{
question: 'What are the contract terms?',
answer: richText(
'Month-to-month with no long-term commitment. Contract terms start at one day. Give us 30 days notice and we will arrange return of the hardware. Shipping is additional.',
),
},
],
},
// CTA
{
blockType: 'lmCtaBanner',
heading: 'Need a custom deployment?',
subheading: 'Talk to our team about multi-unit rollouts, custom configurations, or enterprise pricing.',
ctaText: 'Contact Us',
ctaLink: '/contact',
sectionBackground: 'yellow',
alignment: 'left',
size: 'small',
},
],
})
// ─── 4. VPS Server Page ────────────────────────────────────────────────────
console.log(' → Creating VPS page...')
await createPageIfNotExists('vps', {
title: 'VPS Servers',
slug: 'vps',
_status: 'published',
publishedAt: new Date().toISOString(),
layout: [
// Alternate Hero
// sectionBackground: 'white' | 'dark' | 'gray'
{
blockType: 'lmAlternateHero',
heading: 'Virtual Private Servers',
description: 'Cloud-hosted servers on Layer & Mesh infrastructure. Full root access, instant provisioning, and Swedish-sovereign data. Configure your resources below — pricing updates in real-time.',
primaryCtaText: 'Configure Below',
primaryCtaLink: '#calculator',
secondaryCtaText: 'Contact Sales',
secondaryCtaLink: '/contact',
sectionBackground: 'dark',
},
// VPS Calculator
// sectionBackground: 'dark' | 'gray' | 'teal'
// currency: 'usd' | 'sek' | 'eur'
{
blockType: 'lmVpsCalculator',
heading: 'Estimate your cost',
orderCtaText: 'Add to Cart',
contactCtaText: 'Questions?',
contactCtaLink: '/contact',
currency: 'usd',
sectionBackground: 'dark',
pricingCpuPerCore: 12,
pricingRamPerGb: 5,
pricingSsdPerGb: 0.08,
pricingHddPerGb: 0.02,
pricingWindowsLicense: 25,
anchorId: 'calculator',
},
// USP Table — description is richText
// checkColor: 'dark' | 'yellow' | 'gray'
// textColor: 'dark' | 'white'
// sectionBackground: 'white' | 'gray' | 'dark'
{
blockType: 'lmUspTable',
heading: "What's Included",
sectionBackground: 'dark',
checkColor: 'dark',
textColor: 'white',
rows: [
{
title: 'Full Root Access',
description: richText('SSH, VNC, or console — full control of your server.'),
},
{
title: 'Unmetered Bandwidth',
description: richText('No traffic caps or overage fees.'),
},
{
title: 'NVMe Storage',
description: richText('Enterprise-grade SSDs for maximum I/O.'),
},
{
title: 'Instant Provisioning',
description: richText('Your server is ready in under 60 seconds.'),
},
{
title: '99.99% Uptime SLA',
description: richText('Guaranteed availability with automatic failover.'),
},
{
title: 'Swedish Sovereignty',
description: richText('Data stored and processed in Sweden.'),
},
],
},
// CTA
{
blockType: 'lmCtaBanner',
heading: 'Need dedicated hardware?',
subheading: 'Check out the VF Enhet — edge compute deployed at your location.',
ctaText: 'Learn About VF Enhet',
ctaLink: '/virtuell-fiber',
secondaryCtaText: 'Contact Sales',
secondaryCtaLink: '/contact',
sectionBackground: 'yellow',
alignment: 'left',
size: 'small',
},
],
})
// ─── 5. Contact Page ───────────────────────────────────────────────────────
console.log(' → Creating Contact page...')
await createPageIfNotExists('contact', {
title: 'Contact',
slug: 'contact',
_status: 'published',
publishedAt: new Date().toISOString(),
layout: [
// Contact Form
// sectionBackground: 'white' | 'gray' | 'dark' | 'navyGradient'
// layout: 'standard' | 'withImage' | 'card'
{
blockType: 'lmContactForm',
heading: 'Get in Touch',
description: 'Tell us about your infrastructure needs — our team will get back to you within 24 hours.',
form: contactForm.id,
submitText: 'Send Message',
sectionBackground: 'dark',
layout: 'card',
},
// FAQ
{
blockType: 'lmFaq',
heading: 'Common Questions',
theme: 'dark',
items: [
{
question: 'Where are you located?',
answer: richText(
'Layer & Mesh operates infrastructure in Sweden with support available globally. Our US operations serve North American customers.',
),
},
{
question: 'How quickly can I get started?',
answer: richText(
'VPS servers are provisioned instantly. VF Enhet hardware typically ships within 57 business days.',
),
},
{
question: 'Do you offer enterprise pricing?',
answer: richText(
'Yes. Contact us for volume pricing, custom SLAs, and dedicated account management.',
),
},
],
},
],
})
// ─── 6. Header Navigation ─────────────────────────────────────────────────
// Header navItems are flat: { label, type, url } directly on each item
console.log(' → Setting up header...')
await apiUpdateGlobal('header', {
logoLink: { type: 'custom', url: '/' },
navItems: [
{ label: 'Virtuell Fiber', type: 'custom', url: '/virtuell-fiber' },
{ label: 'VPS Servers', type: 'custom', url: '/vps' },
{ label: 'Contact', type: 'custom', url: '/contact' },
],
})
// ─── 7. Footer ────────────────────────────────────────────────────────────
// Footer navItems use link() field: { link: { type, label, url } }
console.log(' → Setting up footer...')
await apiUpdateGlobal('footer', {
logoLink: { type: 'custom', url: '/' },
navItems: [
{ link: { type: 'custom', label: 'Virtuell Fiber', url: '/virtuell-fiber' } },
{ link: { type: 'custom', label: 'VPS Servers', url: '/vps' } },
{ link: { type: 'custom', label: 'Contact', url: '/contact' } },
{ link: { type: 'custom', label: 'Fiber Direkt (Sweden)', url: 'https://fiberdirekt.se', newTab: true } },
],
})
console.log('')
console.log('✅ Seed complete!')
console.log(' Pages: /, /virtuell-fiber, /vps, /contact')
}
seed().catch((err) => {
console.error('❌ Seed failed:', err.message || err)
process.exit(1)
})