Add standalone /carmode route and nginx documentation
- Add /carmode URL that auto-enters car mode without exit option - /carmode uses its own API route (/carmode/api/) for isolation - Hide exit button and disable Escape key on /carmode - Skip lock screen on dedicated car mode URL - Replace macOS/Ubuntu docs with nginx configuration guide Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,80 +0,0 @@
|
||||
News App - macOS Service Management
|
||||
====================================
|
||||
|
||||
Service: com.je.carnews
|
||||
Port: 5555
|
||||
Plist: /Library/LaunchDaemons/com.je.carnews.plist
|
||||
|
||||
|
||||
BASIC COMMANDS
|
||||
--------------
|
||||
|
||||
Start the service:
|
||||
sudo launchctl kickstart system/com.je.carnews
|
||||
|
||||
Stop the service:
|
||||
sudo launchctl kill SIGTERM system/com.je.carnews
|
||||
|
||||
Restart the service:
|
||||
sudo launchctl kickstart -k system/com.je.carnews
|
||||
|
||||
Check status:
|
||||
sudo launchctl print system/com.je.carnews
|
||||
|
||||
Check if running:
|
||||
lsof -i :5555
|
||||
|
||||
|
||||
ADD TO STARTUP (BOOT)
|
||||
---------------------
|
||||
|
||||
1. Copy the plist to LaunchDaemons:
|
||||
sudo cp /opt/homebrew/var/www/news-app/com.je.carnews.plist /Library/LaunchDaemons/
|
||||
|
||||
2. Load the service:
|
||||
sudo launchctl bootstrap system /Library/LaunchDaemons/com.je.carnews.plist
|
||||
|
||||
3. Start it:
|
||||
sudo launchctl kickstart system/com.je.carnews
|
||||
|
||||
The service will now start automatically on boot.
|
||||
|
||||
|
||||
REMOVE FROM STARTUP (BOOT)
|
||||
--------------------------
|
||||
|
||||
1. Stop and unload the service:
|
||||
sudo launchctl bootout system/com.je.carnews
|
||||
|
||||
2. Remove the plist file:
|
||||
sudo rm /Library/LaunchDaemons/com.je.carnews.plist
|
||||
|
||||
The service will no longer start on boot.
|
||||
|
||||
|
||||
VIEW LOGS
|
||||
---------
|
||||
|
||||
Output log:
|
||||
tail -f /opt/homebrew/var/www/news-app/logs/out.log
|
||||
|
||||
Error log:
|
||||
tail -f /opt/homebrew/var/www/news-app/logs/error.log
|
||||
|
||||
|
||||
TROUBLESHOOTING
|
||||
---------------
|
||||
|
||||
If the service fails to start, check:
|
||||
|
||||
1. Node version - the plist uses:
|
||||
/Users/jared/.nvm/versions/node/v22.19.0/bin/node
|
||||
|
||||
2. If you update Node via nvm, rebuild native modules:
|
||||
cd /opt/homebrew/var/www/news-app
|
||||
npm rebuild better-sqlite3
|
||||
|
||||
3. Then update the node path in the plist if needed and reload:
|
||||
sudo launchctl bootout system/com.je.carnews
|
||||
sudo launchctl bootstrap system /Library/LaunchDaemons/com.je.carnews.plist
|
||||
sudo launchctl kickstart system/com.je.carnews
|
||||
88
news-app/How_To_Nginx.txt
Normal file
88
news-app/How_To_Nginx.txt
Normal file
@@ -0,0 +1,88 @@
|
||||
Nginx Configuration for Car News App
|
||||
=====================================
|
||||
|
||||
This app requires two route groups in your nginx server block:
|
||||
|
||||
1. /carnews/ - Full news app with regular and car mode views
|
||||
2. /carmode/ - Standalone car mode display (no exit, no links to main app)
|
||||
|
||||
Both routes share the same backend API server (port 5555) and static files.
|
||||
|
||||
|
||||
NGINX CONFIGURATION
|
||||
-------------------
|
||||
|
||||
Add the following to your server block:
|
||||
|
||||
# --- Car News App ---
|
||||
location = /carnews {
|
||||
return 301 /carnews/;
|
||||
}
|
||||
|
||||
location /carnews/api/ {
|
||||
proxy_pass http://127.0.0.1:5555/api/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 180s;
|
||||
proxy_connect_timeout 180s;
|
||||
proxy_send_timeout 180s;
|
||||
}
|
||||
|
||||
location /carnews/ {
|
||||
alias /var/www/news-feed-car-mode/news-app/dist/;
|
||||
index index.html;
|
||||
try_files $uri $uri/ /carnews/index.html;
|
||||
}
|
||||
|
||||
# --- Car Mode Direct Access ---
|
||||
location = /carmode {
|
||||
return 301 /carmode/;
|
||||
}
|
||||
|
||||
location /carmode/api/ {
|
||||
proxy_pass http://127.0.0.1:5555/api/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 180s;
|
||||
proxy_connect_timeout 180s;
|
||||
proxy_send_timeout 180s;
|
||||
}
|
||||
|
||||
location /carmode/ {
|
||||
alias /var/www/news-feed-car-mode/news-app/dist/;
|
||||
index index.html;
|
||||
try_files $uri $uri/ /carmode/index.html;
|
||||
}
|
||||
|
||||
|
||||
CONFIGURATION NOTES
|
||||
-------------------
|
||||
|
||||
- The API timeout is set to 180s because AI grouping requests can take time
|
||||
- Both /carnews/ and /carmode/ serve the same dist/ folder
|
||||
- The frontend JS detects which URL it's on and adjusts behavior accordingly
|
||||
- proxy_http_version 1.1 is required for proper keep-alive connections
|
||||
|
||||
|
||||
AFTER CHANGES
|
||||
-------------
|
||||
|
||||
Test and reload nginx:
|
||||
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
|
||||
|
||||
BUILDING THE APP
|
||||
----------------
|
||||
|
||||
After code changes, rebuild the frontend:
|
||||
|
||||
cd /var/www/news-feed-car-mode/news-app
|
||||
npm run build
|
||||
@@ -1,61 +0,0 @@
|
||||
Based on the codebase analysis, here's what needs to change for your Ubuntu deployment:
|
||||
|
||||
Required Changes
|
||||
|
||||
Create a systemd service file instead:
|
||||
|
||||
# /etc/systemd/system/carnews.service
|
||||
[Unit]
|
||||
Description=Car News API Server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=<your-ubuntu-user>
|
||||
WorkingDirectory=/path/to/news-app
|
||||
ExecStart=/usr/bin/node server.js
|
||||
Restart=on-failure
|
||||
EnvironmentFile=/path/to/news-app/.env
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
2. Rebuild native modules
|
||||
|
||||
The better-sqlite3 package requires recompilation on Ubuntu:
|
||||
npm rebuild better-sqlite3
|
||||
|
||||
3. Nginx configuration for Ubuntu
|
||||
|
||||
You'll need to create an Nginx config (not included in this repo). Example:
|
||||
|
||||
location /carnews/ {
|
||||
alias /path/to/news-app/dist/;
|
||||
try_files $uri $uri/ /carnews/index.html;
|
||||
}
|
||||
|
||||
location /carnews/api/ {
|
||||
proxy_pass http://localhost:5555/api/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
4. Update paths if directory structure differs
|
||||
|
||||
On Ubuntu: /var/www/news-feed-car-mode/news-app
|
||||
|
||||
No Changes Needed
|
||||
|
||||
- vite.config.js - base: '/carnews/' stays the same
|
||||
- server.js - Port 5555 works as-is
|
||||
- .env file - copied over
|
||||
- All source code - Uses relative paths
|
||||
|
||||
Deployment Steps
|
||||
|
||||
1. Copy the codebase to Ubuntu
|
||||
2. Run npm install and npm rebuild better-sqlite3
|
||||
3. Run npm run build to generate fresh /dist files
|
||||
4. Create the systemd service file
|
||||
5. Configure Nginx with the proxy rules
|
||||
6. Enable and start the service: sudo systemctl enable --now carnews
|
||||
@@ -1,6 +1,10 @@
|
||||
import './style.css'
|
||||
|
||||
const API_BASE = '/carnews/api'
|
||||
// Check if we're on the dedicated car mode URL
|
||||
const isCarModeUrl = window.location.pathname === '/carmode' || window.location.pathname === '/carmode/'
|
||||
|
||||
// Use appropriate API base depending on URL
|
||||
const API_BASE = isCarModeUrl ? '/carmode/api' : '/carnews/api'
|
||||
const app = document.querySelector('#app')
|
||||
|
||||
// Fetch with timeout helper (default 2 minutes for AI calls)
|
||||
@@ -49,15 +53,16 @@ const state = {
|
||||
error: null,
|
||||
filter: 'all',
|
||||
viewMode: 'regular', // 'regular' or 'ai'
|
||||
carMode: false,
|
||||
carMode: isCarModeUrl, // Auto-enter car mode if on /carmode URL
|
||||
carModeIndex: 0,
|
||||
carModeInterval: null,
|
||||
carModeScrollTimeout: null,
|
||||
carModeScrollInterval: null,
|
||||
aiArticleCount: 0,
|
||||
locked: !getCookie('news_feed_unlocked'),
|
||||
locked: isCarModeUrl ? false : !getCookie('news_feed_unlocked'), // Skip lock for car mode URL
|
||||
unlockClicks: 0,
|
||||
isTransitioning: false,
|
||||
isCarModeUrl: isCarModeUrl, // Track if we're on dedicated car mode URL
|
||||
}
|
||||
|
||||
function formatDate(dateString) {
|
||||
@@ -274,11 +279,13 @@ function renderCarMode() {
|
||||
<div class="w-16 h-16 border-4 border-white/30 border-t-white rounded-full animate-spin mx-auto mb-6"></div>
|
||||
<p class="text-xl">${loadingMessage}</p>
|
||||
</div>
|
||||
${!state.isCarModeUrl ? `
|
||||
<button onclick="exitCarMode()" class="absolute top-14 right-6 z-20 p-3 text-white/70 hover:text-white hover:bg-white/10 rounded-full transition-colors" title="Exit Car Mode (Esc)">
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
` : ''}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -291,12 +298,14 @@ function renderCarMode() {
|
||||
<div class="car-mode-blur-2"></div>
|
||||
<div class="car-mode-blur-3"></div>
|
||||
|
||||
${!state.isCarModeUrl ? `
|
||||
<!-- Exit button -->
|
||||
<button onclick="exitCarMode()" class="absolute top-2 right-6 z-20 p-3 text-white/70 hover:text-white hover:bg-white/10 rounded-full transition-colors backdrop-blur-sm bg-black/10" title="Exit Car Mode (Esc)">
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
` : ''}
|
||||
|
||||
<!-- Car mode indicator -->
|
||||
<div class="absolute top-2 left-6 z-20 flex items-center gap-2 text-white/70 backdrop-blur-sm px-3 py-1.5 rounded-full bg-black/10">
|
||||
@@ -599,6 +608,11 @@ window.enterCarMode = async function() {
|
||||
}
|
||||
|
||||
window.exitCarMode = function() {
|
||||
// On dedicated car mode URL, just refresh the page to restart
|
||||
if (state.isCarModeUrl) {
|
||||
window.location.reload()
|
||||
return
|
||||
}
|
||||
state.carMode = false
|
||||
stopCarModeCycle()
|
||||
render()
|
||||
@@ -692,7 +706,10 @@ document.addEventListener('keydown', (e) => {
|
||||
|
||||
switch (e.key) {
|
||||
case 'Escape':
|
||||
// Don't allow escape on dedicated car mode URL
|
||||
if (!state.isCarModeUrl) {
|
||||
window.exitCarMode()
|
||||
}
|
||||
break
|
||||
case 'ArrowRight':
|
||||
case ' ':
|
||||
@@ -704,9 +721,17 @@ document.addEventListener('keydown', (e) => {
|
||||
}
|
||||
})
|
||||
|
||||
// Initialize: either start car mode or fetch regular news
|
||||
if (isCarModeUrl) {
|
||||
// On dedicated car mode URL, directly enter car mode
|
||||
window.enterCarMode()
|
||||
} else {
|
||||
fetchNews()
|
||||
}
|
||||
|
||||
setInterval(() => {
|
||||
// Don't auto-refresh if in car mode (it handles its own data)
|
||||
if (state.carMode) return
|
||||
if (state.viewMode === 'ai') {
|
||||
fetchGroupedNews()
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user