501 lines
18 KiB
JavaScript
501 lines
18 KiB
JavaScript
#!/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
|
||
// sectionBackground: 'white' | 'dark'
|
||
// cartProductType: 'hardware' | 'vps' | 'service'
|
||
{
|
||
blockType: 'lmProductDetail',
|
||
eyebrow: 'Hardware',
|
||
productName: 'VF Enhet',
|
||
description: 'Purpose-built edge compute hardware for virtuell fiber deployment. Plug the VF Enhet into your location, connect it to the Layer & Mesh network, and get instant private infrastructure — no data center required. Each unit runs a full mesh node with encrypted backhaul, local compute, and automatic failover.',
|
||
ctaText: 'Add to Cart',
|
||
ctaLink: '/contact',
|
||
secondaryCtaText: 'Contact Sales',
|
||
secondaryCtaLink: '/contact',
|
||
enableCart: true,
|
||
cartProductName: 'VF Enhet',
|
||
cartPrice: 149,
|
||
cartProductType: 'hardware',
|
||
sectionBackground: 'dark',
|
||
},
|
||
// Tech Properties — maxRows: 4!
|
||
// categoryColor: 'white' | 'dark'
|
||
// valueColor: 'yellow' | 'white' | 'dark'
|
||
// sectionBackground: 'dark' | 'white' | 'gray' | 'yellow'
|
||
{
|
||
blockType: 'lmTechProperties',
|
||
sectionBackground: 'dark',
|
||
categoryColor: 'white',
|
||
valueColor: 'yellow',
|
||
properties: [
|
||
{ category: 'Processor', value: 'ARM Cortex-A76 Quad-Core' },
|
||
{ category: 'Memory', value: '8 GB DDR4' },
|
||
{ category: 'Storage', value: '256 GB NVMe SSD' },
|
||
{ category: 'Network', value: '2.5 GbE + Wi-Fi 6' },
|
||
],
|
||
},
|
||
// Second Tech Properties block for remaining specs
|
||
{
|
||
blockType: 'lmTechProperties',
|
||
sectionBackground: 'dark',
|
||
categoryColor: 'white',
|
||
valueColor: 'white',
|
||
properties: [
|
||
{ category: 'Power', value: '12W idle / 25W peak' },
|
||
{ category: 'Dimensions', value: '145 × 100 × 40 mm' },
|
||
{ category: 'Connectivity', value: 'Encrypted mesh backhaul' },
|
||
{ category: 'Management', value: 'Remote dashboard included' },
|
||
],
|
||
},
|
||
// FAQ — answer is richText
|
||
// theme: 'gray' | 'light' | 'dark'
|
||
{
|
||
blockType: 'lmFaq',
|
||
heading: 'Frequently Asked Questions',
|
||
theme: 'dark',
|
||
items: [
|
||
{
|
||
question: 'What is the VF Enhet?',
|
||
answer: richText(
|
||
'A compact hardware node that connects your location directly to the Layer & Mesh network. It provides edge compute, encrypted connectivity, and mesh routing — all in a box smaller than a paperback book.',
|
||
),
|
||
},
|
||
{
|
||
question: 'Do I need special wiring or a server room?',
|
||
answer: richText(
|
||
'No. The VF Enhet plugs into any standard ethernet connection and power outlet. It runs silently at 12W idle — about the same as a phone charger.',
|
||
),
|
||
},
|
||
{
|
||
question: 'How is this different from a VPS?',
|
||
answer: richText(
|
||
'A VPS runs in our cloud. The VF Enhet runs at your location. Both connect to the same Layer & Mesh network, but the Enhet gives you physical edge compute with lower latency.',
|
||
),
|
||
},
|
||
{
|
||
question: 'Can I cancel?',
|
||
answer: richText(
|
||
'Yes. Month-to-month with no long-term commitment. Give us 30 days notice and we will arrange return of the hardware.',
|
||
),
|
||
},
|
||
],
|
||
},
|
||
// 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 5–7 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)
|
||
})
|