A Val Town application for managing and viewing demos with authentication.
- Authentication: Google OAuth via LastLogin
- Dashboard: User-friendly interface showing system status
- Health Monitoring: Real-time system health checks
- Modular Architecture: Clean separation of concerns
This application uses LastLogin for authentication:
- Login: Users sign in with their Google account
- Protected Routes: All routes require authentication except public endpoints
- Logout: Users can logout via
/auth/logout
(handled automatically by LastLogin) - Session Management: Automatic session validation and renewal
The following routes are accessible without authentication:
/api/health
- System health status
Routes are protected by different authentication mechanisms:
User Authentication (Google OAuth via LastLogin):
/
- Main dashboard (shows user info and system status)/api/*
- API endpoints (except health)/views/*
- View routes including/views/glimpse/:id
/glimpse/*
- Shortcut routes including/glimpse/:id
(equivalent to/views/glimpse/:id
)
Webhook Authentication (X-API-KEY header):
/tasks/*
- Webhook endpoints for external integrations (POST requests only)- GET requests to
/tasks/*
are allowed without authentication for debug endpoints
- GET requests to
├── backend/
│ ├── controllers/ # Business logic controllers
│ ├── routes/ # Route definitions and HTTP handling
│ │ ├── api/ # API endpoints
│ │ ├── glimpse/ # Shortcut glimpse routes
│ │ ├── tasks/ # Task-related routes
│ │ ├── views/ # User-facing views
│ │ └── authCheck.ts # Authentication middleware
│ └── services/ # External service integrations
├── frontend/ # Frontend assets
├── shared/ # Shared utilities
└── main.tsx # Application entry point
The application follows a clean MVC architecture with proper separation of concerns:
- Handles HTTP request/response formatting
- Extracts parameters from requests
- Applies authentication middleware
- Calls controller functions and formats responses
- Manages HTTP status codes and error responses
- Contains business logic and orchestrates service calls
- Returns plain data objects (not HTTP responses)
- Handles data validation and transformation
- Filters sensitive data (e.g., button properties)
- Provides consistent success/error response structure
- Handles external API calls (Notion, databases)
- Manages data persistence
- Returns structured results with success/error information
All controller functions return a consistent structure:
{
success: boolean,
data: any | null,
error: string | null,
details?: string // Additional error context
}
Routes then format these into appropriate HTTP responses.
The application provides multiple routes for accessing page data and user authentication:
-
GET /glimpse/login
- User-specific login redirect- Requires user authentication (Google OAuth via LastLogin)
- Looks up authenticated user's email in
GLANCE_DEMOS_DB_ID
database - If user found: Redirects to user's personal URL
- If user not found: Creates new user record and redirects to
/glimpse/thanks
- Shows detailed error information for debugging database structure issues
-
GET /glimpse/thanks
- New user welcome page- Shows confirmation that user account was created
- Explains next steps (admin review, email with demo link)
- Provides timeline expectations (1-2 business days)
GET /views/glimpse/:id
- Get complete page data with blocks by Notion page IDGET /glimpse/:id
- Same functionality as above, shorter URL
Note: As of the latest update, glimpse endpoints now return complete page data including all blocks recursively fetched from Notion. This provides richer content compared to the previous properties-only behavior.
API endpoints for accessing Notion page data with different levels of detail:
GET /api/demo/:id/properties
- Returns page properties onlyGET /api/demo/:id
- Returns page properties + all blocks recursively
Architecture:
- Routes: Handle HTTP concerns (parameter extraction, response formatting, status codes)
- Controllers: Contain business logic (
getDemoProperties
,getDemoFull
) - Services: Handle Notion API integration
Authentication Behavior:
- Browser requests: Require user authentication (Google OAuth via LastLogin)
- Internal requests: Bypass authentication when called from within the Val (identified by Deno user agent)
Response Format: Routes return the data directly from controllers on success:
{ // Notion page object with properties // For full endpoint: also includes "blocks" array with recursive block data }
On error, routes return:
{ "error": "Error message", "details": "Additional error context" }
Usage Examples:
// Internal call from within Val (no authentication needed)
const response = await fetch('/api/demo/page-id/properties');
const data = await response.json();
// External browser request (requires authentication)
// User must be logged in via Google OAuth
All glimpse routes:
- Require user authentication
- Return complete page data including properties and blocks recursively
- Filter out button properties from Notion page data
- Return standardized JSON responses (except authentication routes which redirect or show HTML)
- Use the same controller functions as the API endpoints for consistency
- Authentication: User must be authenticated via Google OAuth (handled by LastLogin)
- Database Lookup: System queries
GLANCE_DEMOS_DB_ID
database for user's email - User Creation: If not found, creates new user record with email address
- Welcome Page: Redirects to
/glimpse/thanks
with next steps information - Admin Process: Admin reviews new users and adds demo URLs manually
- User Return: User can return to
/glimpse/login
once URL is configured
The GLANCE_DEMOS_DB_ID
database must contain:
- Email property: Contains user's email address (exact match with authenticated email)
- URL property: Contains user's redirect URL (optional for new users)
Supported URL property names: URL
, Link
, Redirect URL
, Demo URL
, url
, link
Supported URL property types: url
, rich_text
, title
The login endpoint provides detailed error information for debugging:
- Missing environment variables
- Database query failures
- User creation failures (falls back to access denied page)
- Invalid or missing URL properties
- URL format validation errors
The dashboard displays both routes in a comparison table for easy testing.
The application is built with:
- Hono: Web framework for routing and middleware
- LastLogin: Authentication service
- TypeScript: Type-safe development
- Val Town: Hosting platform
The application supports webhook endpoints for external integrations (like Notion webhooks):
Set the webhook secret in your environment:
NOTION_WEBHOOK_SECRET=your-secret-key-here
POST /tasks/notion-webhook
- Main webhook endpoint for Notion integrations (requiresX-API-KEY
header)POST /tasks/url
- Updates Notion page URL property with glimpse URL (requiresX-API-KEY
header)POST /tasks/test
- Test endpoint for webhook authentication (requiresX-API-KEY
header)GET /tasks/debug-webhook
- Debug endpoint to check webhook configuration
Webhook endpoints require the X-API-KEY
header:
curl -X POST https://your-val.web.val.run/tasks/test \ -H "X-API-KEY: your-secret-key-here"
Use the webhook testing form in the dashboard:
- Navigate to your dashboard at
/
- Find the "Webhook Endpoint" section
- Enter your
NOTION_WEBHOOK_SECRET
value - Click "Test Webhook" to verify authentication
Configure these environment variables for full functionality:
GLANCE_DEMOS_DB_ID
- Notion database ID for demosGLANCE_CONTENT_DB_ID
- Notion database ID for contentGLANCE_INTERACTIONS_DB_ID
- Notion database ID for interactionsNOTION_API_KEY
- Notion API key for database accessNOTION_WEBHOOK_SECRET
- Secret key for webhook authentication