Files
flyer-crawler.projectium.com/docs/FLYER-URL-CONFIGURATION.md

11 KiB

Flyer URL Configuration

Overview

Flyer image and icon URLs are environment-specific to ensure they point to the correct server for each deployment. Images are served as static files by NGINX from the /flyer-images/ path with 7-day browser caching enabled.

Environment-Specific URLs

Environment Base URL Example
Dev Container https://127.0.0.1 https://127.0.0.1/flyer-images/safeway-flyer.jpg
Test https://flyer-crawler-test.projectium.com https://flyer-crawler-test.projectium.com/flyer-images/safeway-flyer.jpg
Production https://flyer-crawler.projectium.com https://flyer-crawler.projectium.com/flyer-images/safeway-flyer.jpg

Note: The dev container accepts connections to both https://localhost/ and https://127.0.0.1/ thanks to the SSL certificate and NGINX configuration. See SSL Certificate Configuration below.

SSL Certificate Configuration (Dev Container)

The dev container uses self-signed certificates generated by mkcert to enable HTTPS locally. This configuration solves a common mixed-origin SSL issue.

The Problem

When users access the site via https://localhost/ but image URLs in the database use https://127.0.0.1/..., browsers treat these as different origins. This causes ERR_CERT_AUTHORITY_INVALID errors when loading images, even though both hostnames point to the same server.

The Solution

  1. Certificate Generation (Dockerfile.dev):

    mkcert localhost 127.0.0.1 ::1
    

    This creates a certificate with Subject Alternative Names (SANs) for all three hostnames.

  2. NGINX Configuration (docker/nginx/dev.conf):

    server_name localhost 127.0.0.1;
    

    NGINX accepts requests to both hostnames using the same SSL certificate.

How It Works

Component Configuration
SSL Certificate SANs localhost, 127.0.0.1, ::1
NGINX server_name localhost 127.0.0.1
Seed Script URLs Uses https://127.0.0.1 (works with DB constraints)
User Access Either https://localhost/ or https://127.0.0.1/

Why This Matters

  • Database Constraints: The flyers table has CHECK constraints requiring URLs to start with http:// or https://. Relative URLs are not allowed.
  • Consistent Behavior: Users can access the site using either hostname without SSL warnings.
  • Same Certificate: Both hostnames use the same self-signed certificate, eliminating mixed-content errors.

Verifying the Configuration

# Check certificate SANs
podman exec flyer-crawler-dev openssl x509 -in /app/certs/localhost.crt -text -noout | grep -A1 "Subject Alternative Name"

# Expected output:
# X509v3 Subject Alternative Name:
#     DNS:localhost, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1

# Test both hostnames respond
curl -k https://localhost/health
curl -k https://127.0.0.1/health

Troubleshooting SSL Issues

If you encounter ERR_CERT_AUTHORITY_INVALID:

  1. Check NGINX is running: podman exec flyer-crawler-dev nginx -t
  2. Verify certificate exists: podman exec flyer-crawler-dev ls -la /app/certs/
  3. Ensure both hostnames are in server_name: Check /etc/nginx/sites-available/default
  4. Rebuild container if needed: The certificate is generated at build time

To permanently eliminate SSL certificate warnings, install the mkcert CA certificate on your system. This is optional but provides a better development experience.

The CA certificate is located at certs/mkcert-ca.crt in the project root. See certs/README.md for platform-specific installation instructions (Windows, macOS, Linux, Firefox).

After installation:

  • Your browser will trust all mkcert certificates without warnings
  • Both https://localhost/ and https://127.0.0.1/ will work without SSL errors
  • Flyer images will load without ERR_CERT_AUTHORITY_INVALID errors

See also: Debugging Guide - SSL Issues

NGINX Static File Serving

All environments serve flyer images as static files with browser caching:

# Serve flyer images from static storage (7-day cache)
location /flyer-images/ {
    alias /path/to/flyer-images/;
    expires 7d;
    add_header Cache-Control "public, immutable";
}

Directory Paths by Environment

Environment NGINX Alias Path
Dev Container /app/public/flyer-images/
Test /var/www/flyer-crawler-test.projectium.com/flyer-images/
Production /var/www/flyer-crawler.projectium.com/flyer-images/

Configuration

Environment Variable

Set FLYER_BASE_URL in your environment configuration:

# Dev container (.env)
FLYER_BASE_URL=https://localhost

# Test environment
FLYER_BASE_URL=https://flyer-crawler-test.projectium.com

# Production
FLYER_BASE_URL=https://flyer-crawler.projectium.com

Seed Script

The seed script (src/db/seed.ts) automatically uses the correct base URL based on:

  1. FLYER_BASE_URL environment variable (if set)
  2. NODE_ENV value:
    • productionhttps://flyer-crawler.projectium.com
    • testhttps://flyer-crawler-test.projectium.com
    • Default → https://localhost

The seed script also copies test images from src/tests/assets/ to public/flyer-images/:

  • test-flyer-image.jpg - Sample flyer image
  • test-flyer-icon.png - Sample 64x64 icon

Updating Existing Data

If you need to update existing flyer URLs in the database, use the provided SQL script:

Dev Container

# Connect to dev database
podman exec -it flyer-crawler-postgres psql -U postgres -d flyer_crawler_dev

# Run the update (dev container uses HTTPS with self-signed certs)
UPDATE flyers
SET
    image_url = REPLACE(image_url, 'example.com', 'localhost'),
    icon_url = REPLACE(icon_url, 'example.com', 'localhost')
WHERE
    image_url LIKE '%example.com%'
    OR icon_url LIKE '%example.com%';

# Verify
SELECT flyer_id, image_url, icon_url FROM flyers;

Test Environment

# Via SSH
ssh root@projectium.com "psql -U flyer_crawler_test -d flyer-crawler-test -c \"
UPDATE flyers
SET
    image_url = REPLACE(image_url, 'example.com', 'flyer-crawler-test.projectium.com'),
    icon_url = REPLACE(icon_url, 'example.com', 'flyer-crawler-test.projectium.com')
WHERE
    image_url LIKE '%example.com%'
    OR icon_url LIKE '%example.com%';
\""

Production

# Via SSH
ssh root@projectium.com "psql -U flyer_crawler_prod -d flyer-crawler-prod -c \"
UPDATE flyers
SET
    image_url = REPLACE(image_url, 'example.com', 'flyer-crawler.projectium.com'),
    icon_url = REPLACE(icon_url, 'example.com', 'flyer-crawler.projectium.com')
WHERE
    image_url LIKE '%example.com%'
    OR icon_url LIKE '%example.com%';
\""

Test Data Updates

Test Helper Function

A helper function getFlyerBaseUrl() is available in src/tests/utils/testHelpers.ts that automatically detects the correct base URL for tests:

export const getFlyerBaseUrl = (): string => {
  if (process.env.FLYER_BASE_URL) {
    return process.env.FLYER_BASE_URL;
  }

  // Check if we're in dev container (DB_HOST=postgres is typical indicator)
  // Use 'localhost' instead of '127.0.0.1' to match the hostname users access
  // This avoids SSL certificate mixed-origin issues in browsers
  if (process.env.DB_HOST === 'postgres' || process.env.DB_HOST === '127.0.0.1') {
    return 'https://localhost';
  }

  if (process.env.NODE_ENV === 'production') {
    return 'https://flyer-crawler.projectium.com';
  }

  if (process.env.NODE_ENV === 'test') {
    return 'https://flyer-crawler-test.projectium.com';
  }

  // Default for unit tests
  return 'https://example.com';
};

Updated Test Files

The following test files now use getFlyerBaseUrl() for environment-aware URL generation:

This approach ensures tests work correctly in all environments (dev container, CI/CD, local development, test, production).

Files Changed

File Change
src/db/seed.ts Added FLYER_BASE_URL environment variable support, copies test images to public/flyer-images/
docker/nginx/dev.conf Added /flyer-images/ location block for static file serving
.env.example Added FLYER_BASE_URL variable
sql/update_flyer_urls.sql SQL script for updating existing data
Test files Updated mock data to use https://localhost

Summary

  • Seed script now uses environment-specific HTTPS URLs
  • Seed script copies test images from src/tests/assets/ to public/flyer-images/
  • NGINX serves /flyer-images/ as static files with 7-day cache
  • Test files updated with https://localhost (not 127.0.0.1 to avoid SSL mixed-origin issues)
  • SQL script provided for updating existing data
  • Documentation updated for each environment