Migrate kneadly to kneadly.bored.investments

Move off the political brand (neutral SaaS). New canonical home is
kneadly.bored.investments; BASE_URL + fallbacks repointed. Old host stays
live and 301s human-facing paths across (Google Maps booking links survive),
while /webhooks + /api keep serving on the old domain for un-repointed integrations.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
King Omar 2026-07-24 23:47:05 +10:00
parent dbee47f156
commit 7a7be3fc03
4 changed files with 29 additions and 6 deletions

View file

@ -9,6 +9,22 @@ import publicRoutes from './routes/public.js'
const app = new Hono() const app = new Hono()
// Canonical-domain redirect: Kneadly moved from theradicalparty.com to
// bored.investments (neutral SaaS, off the political brand). Keep the old host
// alive so existing links — including Google Maps booking links — 301 across.
app.use('*', async (c, next) => {
const url = new URL(c.req.url)
// Skip machine endpoints: Stripe webhooks + API callers don't follow 301s,
// and both hosts hit the same Worker + D1, so those keep working on either
// domain until integrations are repointed.
const machine = url.pathname.startsWith('/webhooks') || url.pathname.startsWith('/api')
if (url.hostname === 'kneadly.theradicalparty.com' && !machine) {
url.hostname = 'kneadly.bored.investments'
return c.redirect(url.toString(), 301)
}
await next()
})
// Attach logged-in owner (if any) to the request context // Attach logged-in owner (if any) to the request context
app.use('*', async (c, next) => { app.use('*', async (c, next) => {
const sessionId = getCookie(c, 'kneadly_session') const sessionId = getCookie(c, 'kneadly_session')
@ -39,12 +55,12 @@ app.get('/og.svg', (c) => {
}) })
app.get('/robots.txt', (c) => { app.get('/robots.txt', (c) => {
const base = c.env.BASE_URL || 'https://kneadly.theradicalparty.com' const base = c.env.BASE_URL || 'https://kneadly.bored.investments'
return c.text(`User-agent: *\nAllow: /\nDisallow: /dashboard\nDisallow: /api\nDisallow: /webhooks\nSitemap: ${base}/sitemap.xml`) return c.text(`User-agent: *\nAllow: /\nDisallow: /dashboard\nDisallow: /api\nDisallow: /webhooks\nSitemap: ${base}/sitemap.xml`)
}) })
app.get('/sitemap.xml', async (c) => { app.get('/sitemap.xml', async (c) => {
const base = c.env.BASE_URL || 'https://kneadly.theradicalparty.com' const base = c.env.BASE_URL || 'https://kneadly.bored.investments'
const rows = await c.env.DB.prepare( const rows = await c.env.DB.prepare(
`SELECT slug FROM shops WHERE is_published = 1 ORDER BY created_at DESC`).all() `SELECT slug FROM shops WHERE is_published = 1 ORDER BY created_at DESC`).all()
const urls = [`<url><loc>${base}/</loc><priority>1.0</priority></url>`] const urls = [`<url><loc>${base}/</loc><priority>1.0</priority></url>`]

View file

@ -60,7 +60,7 @@ const upcomingWhere = `status IN ('confirmed','pending_payment') AND start_time
// ─── Overview ──────────────────────────────────────────────────────────────── // ─── Overview ────────────────────────────────────────────────────────────────
app.get('/', async (c) => { app.get('/', async (c) => {
const db = c.env.DB, shop = c.get('shop') const db = c.env.DB, shop = c.get('shop')
const base = c.env.BASE_URL || 'https://kneadly.theradicalparty.com' const base = c.env.BASE_URL || 'https://kneadly.bored.investments'
const link = `${base}/${shop.slug}` const link = `${base}/${shop.slug}`
const welcome = c.req.query('welcome') const welcome = c.req.query('welcome')

View file

@ -107,7 +107,7 @@ app.get('/:slug', async (c) => {
const staff = (await db.prepare( const staff = (await db.prepare(
'SELECT * FROM staff WHERE shop_id = ? AND is_active = 1 ORDER BY sort_order, created_at').bind(shop.id).all()).results || [] 'SELECT * FROM staff WHERE shop_id = ? AND is_active = 1 ORDER BY sort_order, created_at').bind(shop.id).all()).results || []
const base = c.env.BASE_URL || 'https://kneadly.theradicalparty.com' const base = c.env.BASE_URL || 'https://kneadly.bored.investments'
const addr = [shop.address, shop.suburb, shop.state, shop.postcode].filter(Boolean).join(', ') const addr = [shop.address, shop.suburb, shop.state, shop.postcode].filter(Boolean).join(', ')
const jsonld = { const jsonld = {
@ -310,7 +310,7 @@ app.post('/:slug/book', async (c) => {
// Payment required → Stripe Checkout // Payment required → Stripe Checkout
if (status === 'pending_payment') { if (status === 'pending_payment') {
const base = c.env.BASE_URL || 'https://kneadly.theradicalparty.com' const base = c.env.BASE_URL || 'https://kneadly.bored.investments'
try { try {
const session = await stripeClient(c.env.STRIPE_SECRET_KEY).createCheckoutSession({ const session = await stripeClient(c.env.STRIPE_SECRET_KEY).createCheckoutSession({
mode: 'payment', mode: 'payment',

View file

@ -8,9 +8,16 @@ binding = "DB"
database_name = "kneadly" database_name = "kneadly"
database_id = "795c6a38-c6e9-4221-b429-4890a39a8934" database_id = "795c6a38-c6e9-4221-b429-4890a39a8934"
# New canonical home (neutral SaaS, off the political brand).
[[routes]]
pattern = "kneadly.bored.investments"
custom_domain = true
# Old home kept alive so existing links (incl. Google Maps) 301 across; the
# worker still serves /webhooks + /api here for not-yet-repointed integrations.
[[routes]] [[routes]]
pattern = "kneadly.theradicalparty.com" pattern = "kneadly.theradicalparty.com"
custom_domain = true custom_domain = true
[vars] [vars]
BASE_URL = "https://kneadly.theradicalparty.com" BASE_URL = "https://kneadly.bored.investments"