v0.9.4 bugs fixes

This commit is contained in:
2026-03-14 00:33:53 -04:00
parent 28678dc5b0
commit e7f1bdb195
8 changed files with 67 additions and 39 deletions

View File

@@ -1,15 +1,33 @@
# jama Configuration # ─────────────────────────────────────────────────────────────
# jama — Configuration
# just another messaging app # 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) # Project name — used as the Docker container name.
# Run 'timedatectl' on your host to find the correct value # If you run multiple jama instances on the same host, give each a unique name.
TZ=UTC PROJECT_NAME=jama
# Copy this file to .env and customize
# Image version to run (set by build.sh, or use 'latest') # 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_NAME=Admin User
ADMIN_EMAIL=admin@jama.local ADMIN_EMAIL=admin@jama.local
ADMIN_PASS=Admin@1234 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) # Default password for bulk-imported users (when no password is set in CSV)
USER_PASS=user@1234 USER_PASS=user@1234
# Set to true to reset admin password to ADMIN_PASS on every restart # Set to true to reset the admin password to ADMIN_PASS on every restart.
# WARNING: Leave false in production - shows a warning on login page when true # WARNING: Leave false in production shows a warning banner on the login page when true.
ADMPW_RESET=false 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 JWT_SECRET=changeme_super_secret_jwt_key_change_in_production
# Database encryption key (SQLCipher AES-256) # Database encryption key (SQLCipher AES-256)
# Generate a strong random key: openssl rand -hex 32 # Generate a strong key: openssl rand -hex 32
# IMPORTANT: If you are upgrading from an unencrypted install, run the # Leave blank to run without encryption (not recommended for production).
# migration script first: node scripts/encrypt-db.js #
# 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 <project>_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= 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

View File

@@ -1,6 +1,6 @@
{ {
"name": "jama-backend", "name": "jama-backend",
"version": "0.9.3", "version": "0.9.4",
"description": "TeamChat backend server", "description": "TeamChat backend server",
"main": "src/index.js", "main": "src/index.js",
"scripts": { "scripts": {

View File

@@ -28,9 +28,14 @@ function getDb() {
} else { } else {
console.warn('[DB] WARNING: DB_KEY not set — database is unencrypted'); 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'); 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; return db;
} }

View File

@@ -70,12 +70,10 @@ router.post('/subscribe', authMiddleware, (req, res) => {
return res.status(400).json({ error: 'Invalid subscription' }); return res.status(400).json({ error: 'Invalid subscription' });
} }
const db = getDb(); const db = getDb();
// Use DELETE+INSERT to avoid relying on any specific UNIQUE constraint const device = req.device || 'desktop';
// (existing DBs may have different schemas for this table) // Delete any existing subscription for this user+device or this endpoint, then insert fresh
const delStmt = db.prepare('DELETE FROM push_subscriptions WHERE endpoint = ?'); db.prepare('DELETE FROM push_subscriptions WHERE endpoint = ? OR (user_id = ? AND device = ?)').run(endpoint, req.user.id, device);
const insStmt = db.prepare('INSERT INTO push_subscriptions (user_id, endpoint, p256dh, auth) VALUES (?, ?, ?, ?)'); db.prepare('INSERT INTO push_subscriptions (user_id, device, endpoint, p256dh, auth) VALUES (?, ?, ?, ?, ?)').run(req.user.id, device, endpoint, keys.p256dh, keys.auth);
delStmt.run(endpoint);
insStmt.run(req.user.id, endpoint, keys.p256dh, keys.auth);
res.json({ success: true }); res.json({ success: true });
}); });

View File

@@ -13,7 +13,7 @@
# ───────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────
set -euo pipefail set -euo pipefail
VERSION="${1:-0.9.3}" VERSION="${1:-0.9.4}"
ACTION="${2:-}" ACTION="${2:-}"
REGISTRY="${REGISTRY:-}" REGISTRY="${REGISTRY:-}"
IMAGE_NAME="jama" IMAGE_NAME="jama"

View File

@@ -1,9 +1,7 @@
version: '3.8'
services: services:
jama: jama:
image: jama:${JAMA_VERSION:-latest} image: jama:${JAMA_VERSION:-latest}
container_name: jama container_name: ${PROJECT_NAME:-jama}
restart: unless-stopped restart: unless-stopped
ports: ports:
- "${PORT:-3000}:3000" - "${PORT:-3000}:3000"
@@ -16,9 +14,9 @@ services:
- USER_PASS=${USER_PASS:-user@1234} - USER_PASS=${USER_PASS:-user@1234}
- ADMPW_RESET=${ADMPW_RESET:-false} - ADMPW_RESET=${ADMPW_RESET:-false}
- JWT_SECRET=${JWT_SECRET:-changeme_super_secret_jwt_key_2024} - JWT_SECRET=${JWT_SECRET:-changeme_super_secret_jwt_key_2024}
- DB_KEY=${DB_KEY}
- APP_NAME=${APP_NAME:-jama} - APP_NAME=${APP_NAME:-jama}
- DEFCHAT_NAME=${DEFCHAT_NAME:-General Chat} - DEFCHAT_NAME=${DEFCHAT_NAME:-General Chat}
- DB_KEY=${DB_KEY:-}
volumes: volumes:
- jama_db:/app/data - jama_db:/app/data
- jama_uploads:/app/uploads - jama_uploads:/app/uploads

View File

@@ -1,6 +1,6 @@
{ {
"name": "jama-frontend", "name": "jama-frontend",
"version": "0.9.3", "version": "0.9.4",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -6,6 +6,7 @@ import { useAuth } from '../contexts/AuthContext.jsx';
import { useToast } from '../contexts/ToastContext.jsx'; import { useToast } from '../contexts/ToastContext.jsx';
import { useSocket } from '../contexts/SocketContext.jsx'; import { useSocket } from '../contexts/SocketContext.jsx';
import './ChatWindow.css'; import './ChatWindow.css';
import GroupInfoModal from './GroupInfoModal.jsx';
export default function ChatWindow({ group, onBack, onGroupUpdated, onDirectMessage, onlineUserIds = new Set() }) { export default function ChatWindow({ group, onBack, onGroupUpdated, onDirectMessage, onlineUserIds = new Set() }) {
const { user: currentUser } = useAuth(); const { user: currentUser } = useAuth();
@@ -17,6 +18,7 @@ export default function ChatWindow({ group, onBack, onGroupUpdated, onDirectMess
const [hasMore, setHasMore] = useState(false); const [hasMore, setHasMore] = useState(false);
const [typing, setTyping] = useState([]); const [typing, setTyping] = useState([]);
const [iconGroupInfo, setIconGroupInfo] = useState(''); const [iconGroupInfo, setIconGroupInfo] = useState('');
const [showInfo, setShowInfo] = useState(false);
const [isMobile, setIsMobile] = useState(window.innerWidth < 768); const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
const messagesEndRef = useRef(null); const messagesEndRef = useRef(null);
@@ -231,7 +233,7 @@ export default function ChatWindow({ group, onBack, onGroupUpdated, onDirectMess
{!isDirect && ( {!isDirect && (
<button <button
className="btn-icon" className="btn-icon"
onClick={() => window.dispatchEvent(new CustomEvent('jama:group-info', { detail: group }))} onClick={() => setShowInfo(true)}
title="Group info" title="Group info"
> >
{iconGroupInfo ? ( {iconGroupInfo ? (
@@ -293,5 +295,15 @@ export default function ChatWindow({ group, onBack, onGroupUpdated, onDirectMess
<MessageInput group={group} currentUser={currentUser} onSend={handleSend} socket={socket} /> <MessageInput group={group} currentUser={currentUser} onSend={handleSend} socket={socket} />
)} )}
</div> </div>
{showInfo && (
<GroupInfoModal
group={group}
onClose={() => setShowInfo(false)}
onUpdated={(updatedGroup) => { setShowInfo(false); onGroupUpdated && onGroupUpdated(updatedGroup); }}
onBack={() => setShowInfo(false)}
/>
)}
</div>
); );
} }