From e7f1bdb195b0de4b281fbe8095f039901d56b896 Mon Sep 17 00:00:00 2001 From: Ricky Stretch Date: Sat, 14 Mar 2026 00:33:53 -0400 Subject: [PATCH] v0.9.4 bugs fixes --- .env.example | 61 ++++++++++++++++---------- backend/package.json | 2 +- backend/src/models/db.js | 9 +++- backend/src/routes/push.js | 10 ++--- build.sh | 2 +- docker-compose.yaml | 6 +-- frontend/package.json | 2 +- frontend/src/components/ChatWindow.jsx | 14 +++++- 8 files changed, 67 insertions(+), 39 deletions(-) diff --git a/.env.example b/.env.example index eacec63..809263e 100644 --- a/.env.example +++ b/.env.example @@ -1,15 +1,33 @@ -# jama Configuration +# ───────────────────────────────────────────────────────────── +# jama — Configuration # just another messaging app +# +# Copy this file to .env and customize before first run. +# ───────────────────────────────────────────────────────────── -# Timezone — must match your host timezone (e.g. America/Toronto, Europe/London, Asia/Tokyo) -# Run 'timedatectl' on your host to find the correct value -TZ=UTC -# Copy this file to .env and customize +# Project name — used as the Docker container name. +# If you run multiple jama instances on the same host, give each a unique name. +PROJECT_NAME=jama # Image version to run (set by build.sh, or use 'latest') -JAMA_VERSION=0.9.3 +JAMA_VERSION=0.9.4 -# Default admin credentials (used on FIRST RUN only) +# App port — the host port Docker maps to the container +PORT=3000 + +# Timezone — must match your host timezone +# Run 'timedatectl' on Linux or 'ls /usr/share/zoneinfo' to find your value +# Examples: America/Toronto, Europe/London, Asia/Tokyo +TZ=UTC + +# ── App ─────────────────────────────────────────────────────── +# App name (can also be changed in the Settings UI after first run) +APP_NAME=jama + +# Default public group name (created on first run only) +DEFCHAT_NAME=General Chat + +# ── Admin credentials (used on FIRST RUN only) ──────────────── ADMIN_NAME=Admin User ADMIN_EMAIL=admin@jama.local ADMIN_PASS=Admin@1234 @@ -17,25 +35,22 @@ ADMIN_PASS=Admin@1234 # Default password for bulk-imported users (when no password is set in CSV) USER_PASS=user@1234 -# Set to true to reset admin password to ADMIN_PASS on every restart -# WARNING: Leave false in production - shows a warning on login page when true +# Set to true to reset the admin password to ADMIN_PASS on every restart. +# WARNING: Leave false in production — shows a warning banner on the login page when true. ADMPW_RESET=false -# JWT secret - change this to a random string in production! +# ── Security ────────────────────────────────────────────────── +# JWT secret — change this to a long random string in production! +# Generate one: openssl rand -hex 32 JWT_SECRET=changeme_super_secret_jwt_key_change_in_production # Database encryption key (SQLCipher AES-256) -# Generate a strong random key: openssl rand -hex 32 -# IMPORTANT: If you are upgrading from an unencrypted install, run the -# migration script first: node scripts/encrypt-db.js -# Leave blank to run without encryption (not recommended for production) +# Generate a strong key: openssl rand -hex 32 +# Leave blank to run without encryption (not recommended for production). +# +# IMPORTANT — upgrading an existing unencrypted install: +# 1. docker compose down +# 2. Find your DB: docker volume inspect _jama_db +# 3. node backend/scripts/encrypt-db.js --db /path/to/jama.db --key YOUR_KEY +# 4. Add DB_KEY=YOUR_KEY here, then: ./build.sh && docker compose up -d DB_KEY= - -# App port (default 3000) -PORT=3000 - -# App name (can also be changed in Settings UI) - -# Default public group name (created on first run only) -DEFCHAT_NAME=General Chat -APP_NAME=jama diff --git a/backend/package.json b/backend/package.json index a24ffb3..1f05cc1 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "jama-backend", - "version": "0.9.3", + "version": "0.9.4", "description": "TeamChat backend server", "main": "src/index.js", "scripts": { diff --git a/backend/src/models/db.js b/backend/src/models/db.js index 639b7b0..6f3a37c 100644 --- a/backend/src/models/db.js +++ b/backend/src/models/db.js @@ -28,9 +28,14 @@ function getDb() { } else { console.warn('[DB] WARNING: DB_KEY not set — database is unencrypted'); } - db.pragma('journal_mode = WAL'); + const journalMode = db.pragma('journal_mode = WAL', { simple: true }); + if (journalMode !== 'wal') { + console.warn(`[DB] WARNING: journal_mode is '${journalMode}', expected 'wal' — performance may be degraded`); + } + db.pragma('synchronous = NORMAL'); // safe with WAL, faster than FULL + db.pragma('cache_size = -8000'); // 8MB page cache db.pragma('foreign_keys = ON'); - console.log(`[DB] Opened database at ${DB_PATH}`); + console.log(`[DB] Opened database at ${DB_PATH} (journal=${journalMode})`); } return db; } diff --git a/backend/src/routes/push.js b/backend/src/routes/push.js index a4c5c5a..76010ab 100644 --- a/backend/src/routes/push.js +++ b/backend/src/routes/push.js @@ -70,12 +70,10 @@ router.post('/subscribe', authMiddleware, (req, res) => { return res.status(400).json({ error: 'Invalid subscription' }); } const db = getDb(); - // Use DELETE+INSERT to avoid relying on any specific UNIQUE constraint - // (existing DBs may have different schemas for this table) - const delStmt = db.prepare('DELETE FROM push_subscriptions WHERE endpoint = ?'); - const insStmt = db.prepare('INSERT INTO push_subscriptions (user_id, endpoint, p256dh, auth) VALUES (?, ?, ?, ?)'); - delStmt.run(endpoint); - insStmt.run(req.user.id, endpoint, keys.p256dh, keys.auth); + const device = req.device || 'desktop'; + // Delete any existing subscription for this user+device or this endpoint, then insert fresh + db.prepare('DELETE FROM push_subscriptions WHERE endpoint = ? OR (user_id = ? AND device = ?)').run(endpoint, req.user.id, device); + db.prepare('INSERT INTO push_subscriptions (user_id, device, endpoint, p256dh, auth) VALUES (?, ?, ?, ?, ?)').run(req.user.id, device, endpoint, keys.p256dh, keys.auth); res.json({ success: true }); }); diff --git a/build.sh b/build.sh index b3450ca..3cd4b44 100644 --- a/build.sh +++ b/build.sh @@ -13,7 +13,7 @@ # ───────────────────────────────────────────────────────────── set -euo pipefail -VERSION="${1:-0.9.3}" +VERSION="${1:-0.9.4}" ACTION="${2:-}" REGISTRY="${REGISTRY:-}" IMAGE_NAME="jama" diff --git a/docker-compose.yaml b/docker-compose.yaml index 940d55b..6917806 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,9 +1,7 @@ -version: '3.8' - services: jama: image: jama:${JAMA_VERSION:-latest} - container_name: jama + container_name: ${PROJECT_NAME:-jama} restart: unless-stopped ports: - "${PORT:-3000}:3000" @@ -16,9 +14,9 @@ services: - USER_PASS=${USER_PASS:-user@1234} - ADMPW_RESET=${ADMPW_RESET:-false} - JWT_SECRET=${JWT_SECRET:-changeme_super_secret_jwt_key_2024} + - DB_KEY=${DB_KEY} - APP_NAME=${APP_NAME:-jama} - DEFCHAT_NAME=${DEFCHAT_NAME:-General Chat} - - DB_KEY=${DB_KEY:-} volumes: - jama_db:/app/data - jama_uploads:/app/uploads diff --git a/frontend/package.json b/frontend/package.json index 7202050..1db216a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "jama-frontend", - "version": "0.9.3", + "version": "0.9.4", "private": true, "scripts": { "dev": "vite", diff --git a/frontend/src/components/ChatWindow.jsx b/frontend/src/components/ChatWindow.jsx index c7fffbe..2acd90d 100644 --- a/frontend/src/components/ChatWindow.jsx +++ b/frontend/src/components/ChatWindow.jsx @@ -6,6 +6,7 @@ import { useAuth } from '../contexts/AuthContext.jsx'; import { useToast } from '../contexts/ToastContext.jsx'; import { useSocket } from '../contexts/SocketContext.jsx'; import './ChatWindow.css'; +import GroupInfoModal from './GroupInfoModal.jsx'; export default function ChatWindow({ group, onBack, onGroupUpdated, onDirectMessage, onlineUserIds = new Set() }) { const { user: currentUser } = useAuth(); @@ -17,6 +18,7 @@ export default function ChatWindow({ group, onBack, onGroupUpdated, onDirectMess const [hasMore, setHasMore] = useState(false); const [typing, setTyping] = useState([]); const [iconGroupInfo, setIconGroupInfo] = useState(''); + const [showInfo, setShowInfo] = useState(false); const [isMobile, setIsMobile] = useState(window.innerWidth < 768); const messagesEndRef = useRef(null); @@ -231,7 +233,7 @@ export default function ChatWindow({ group, onBack, onGroupUpdated, onDirectMess {!isDirect && (