Skip to content

Trip Planner

AI-powered travel planning application with interactive maps and itinerary management.


Production

  • App: https://travel.bogocat.com (via Caddy reverse proxy)
  • Namespace: trip-planner
  • Ingress: nginx class, hostname travel.bogocat.com

Development

  • Dev Server: http://localhost:3000 (run from host)
  • Host Path: /root/projects/trip-planner
  • tmux Session: tmux attach -t trip-planner

Overview

Trip Planner helps users plan and organize travel itineraries with AI assistance, interactive maps, and collaborative features.

Key Features

  • AI Chat Interface: Natural language trip planning with Claude (Anthropic)
  • Interactive Maps: Mapbox integration for location visualization
  • Itinerary Management: Day-by-day planning with locations and activities
  • Location Management: Add, edit, and organize trip locations
  • GPX Import: Import hiking/biking routes from GPX files
  • Mobile Navigation: Responsive bottom navigation for mobile devices
  • Today View: Active trip detection with "what's next" display, countdowns, and one-tap navigation

Tech Stack

  • Framework: Next.js 16 (App Router)
  • UI Library: React 19
  • Styling: Tailwind CSS v4
  • Database: Supabase (PostgreSQL, k8s shared instance)
  • Authentication: Authentik SSO via NextAuth v5 (dual auth mode)
  • Maps: Mapbox GL JS + react-map-gl
  • AI: @jakecelentano/ai-providers (npm) + Anthropic Claude
  • Deployment: Kubernetes (k3s) with NGINX Ingress + Caddy

Authentication

Dual Auth Mode

Trip Planner uses the standard Authentik SSO pattern with dual auth mode:

  • AUTH_MODE=none - Development mode, no login required
  • AUTH_MODE=authentik - Production mode, requires Authentik SSO

Supabase Auth Bridge

Since Authentik uses SHA256 hashes for user IDs (not UUIDs), a custom auth bridge generates Supabase-compatible JWTs:

File: lib/auth/supabase-bridge.ts

// Usage in server components/API routes:
const { user, supabase } = await getAuthContextWithRLS()

if (!user || !supabase) {
  redirect("/login")
}

// supabase client has RLS-enabled JWT with user's Authentik ID
const { data } = await supabase.from("trips").select("*")

RLS Policies

RLS policies use auth.jwt() ->> 'sub' instead of auth.uid() to match Authentik's sub claim format:

CREATE POLICY trips_user_policy ON trips
  FOR ALL
  USING (auth.jwt() ->> 'sub' = user_id);

Database

Schema

Tables organized in the trip_planner PostgreSQL schema:

-- Main tables
trip_planner.trips           -- user_id is TEXT (Authentik hash)
trip_planner.itineraries
trip_planner.locations
trip_planner.constraints
trip_planner.chat_messages
trip_planner.chat_sessions

Supabase Instance: Shared Kubernetes Supabase (supabase namespace)

Client Configuration:

const supabase = createClient(url, anonKey, {
  db: { schema: 'trip_planner' }
})

See: Supabase Multi-App Architecture


Deployment

Deploy Script

# Full deployment
/root/tower-fleet/scripts/deploy-trip-planner.sh -y

# Or step by step:
cd /root/projects/trip-planner
AUTH_MODE=authentik npm run build
docker build -t trip-planner:v1.x.x .
docker tag trip-planner:v1.x.x 10.89.97.201:30500/trip-planner:v1.x.x
docker push 10.89.97.201:30500/trip-planner:v1.x.x
kubectl set image deployment/trip-planner trip-planner=10.89.97.201:30500/trip-planner:v1.x.x -n trip-planner

Dockerfile Notes

The Dockerfile must set AUTH_MODE=authentik during build:

FROM base AS builder
# ...
ENV AUTH_MODE=authentik
RUN npm run build

Container Registry

Registry: 10.89.97.201:30500

Images: - 10.89.97.201:30500/trip-planner:latest - 10.89.97.201:30500/trip-planner:v1.x.x


Environment Variables

Development (.env.local)

AUTH_MODE=none  # or authentik for testing SSO
NEXT_PUBLIC_SUPABASE_URL=http://10.89.97.214:8000
NEXT_PUBLIC_SUPABASE_ANON_KEY=<anon-key>
SUPABASE_SERVICE_ROLE_KEY=<service-role-key>
SUPABASE_JWT_SECRET=<jwt-secret>
ANTHROPIC_API_KEY=<api-key>
# For AUTH_MODE=authentik:
AUTH_SECRET=<random-secret>
AUTHENTIK_CLIENT_ID=<client-id>
AUTHENTIK_CLIENT_SECRET=<client-secret>
AUTHENTIK_ISSUER=https://auth.bogocat.com/application/o/trip-planner/

Production (k8s Secrets)

All sensitive values stored in trip-planner-secrets SealedSecret: - AUTH_MODE, AUTH_SECRET, AUTH_URL, AUTH_TRUST_HOST - AUTHENTIK_CLIENT_ID, AUTHENTIK_CLIENT_SECRET, AUTHENTIK_ISSUER - SUPABASE_JWT_SECRET, SUPABASE_SERVICE_ROLE_KEY - ANTHROPIC_API_KEY


Dependencies

@jakecelentano/ai-providers

Custom npm package for AI provider abstraction:

npm install @jakecelentano/ai-providers@^1.1.0

Published: https://www.npmjs.com/package/@jakecelentano/ai-providers

Provides unified interface for Anthropic and OpenRouter APIs.


Troubleshooting

"Dev Mode" showing in production

Check that: 1. AUTH_MODE=authentik is in k8s secret 2. SessionProvider wraps the app in layout.tsx 3. /api/auth/mode returns {"mode":"authentik"}

Trips not loading (RLS errors)

  1. Verify user_id column is TEXT type (not UUID)
  2. Check RLS policies use auth.jwt() ->> 'sub'
  3. Ensure SUPABASE_JWT_SECRET matches Supabase's JWT secret

Docker build fails

See: Docker AppArmor Issue on PVE9



Last Updated: 2025-12-23 Status: Active Version: v1.1.0 Production URL: https://travel.bogocat.com