v0.9.4 bugs fixes
This commit is contained in:
61
.env.example
61
.env.example
@@ -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
|
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
2
build.sh
2
build.sh
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user